diff --git a/libraries/stdlib/js-v1/src/js/polyfills.js b/libraries/stdlib/js-v1/src/js/polyfills.js index 7be6ac057d6..f2e119914ba 100644 --- a/libraries/stdlib/js-v1/src/js/polyfills.js +++ b/libraries/stdlib/js-v1/src/js/polyfills.js @@ -230,6 +230,17 @@ if (typeof Math.log2 === "undefined") { return Math.log(x) * Math.LOG2E; }; } +if (typeof Math.clz32 === "undefined") { + Math.clz32 = (function(log, LN2) { + return function(x) { + var asUint = x >>> 0; + if (asUint === 0) { + return 32; + } + return 31 - (log(asUint) / LN2 | 0) | 0; // the "| 0" acts like math.floor + }; + })(Math.log, Math.LN2); +} // For HtmlUnit and PhantomJs if (typeof ArrayBuffer.isView === "undefined") { diff --git a/libraries/stdlib/js-v1/src/kotlin/numbers_js-v1.kt b/libraries/stdlib/js-v1/src/kotlin/numbers_js-v1.kt index fd83ff56fbc..63f5eab72e7 100644 --- a/libraries/stdlib/js-v1/src/kotlin/numbers_js-v1.kt +++ b/libraries/stdlib/js-v1/src/kotlin/numbers_js-v1.kt @@ -58,4 +58,9 @@ public actual fun Float.toRawBits(): Int = definedExternally */ @SinceKotlin("1.2") @kotlin.internal.InlineOnly -public actual inline fun Float.Companion.fromBits(bits: Int): Float = js("Kotlin").floatFromBits(bits).unsafeCast() \ No newline at end of file +public actual inline fun Float.Companion.fromBits(bits: Int): Float = js("Kotlin").floatFromBits(bits).unsafeCast() + + +internal inline fun Long(low: Int, high: Int) = js("Kotlin").Long.fromBits(low, high).unsafeCast() +internal inline val Long.low: Int get() = this.asDynamic().getLowBits().unsafeCast() +internal inline val Long.high: Int get() = this.asDynamic().getHighBits().unsafeCast() \ No newline at end of file diff --git a/libraries/stdlib/js/src/kotlin/js.math.kt b/libraries/stdlib/js/src/kotlin/js.math.kt index f2031dd7dd6..50d0f4465b1 100644 --- a/libraries/stdlib/js/src/kotlin/js.math.kt +++ b/libraries/stdlib/js/src/kotlin/js.math.kt @@ -87,6 +87,9 @@ public external object Math { internal fun log2(value: Double): Double @PublishedApi internal fun log1p(value: Double): Double + + @PublishedApi + internal fun clz32(value: Int): Int } /** diff --git a/libraries/stdlib/js/src/kotlin/numbers.kt b/libraries/stdlib/js/src/kotlin/numbers.kt index 234b86c291a..ac7ffe3d94c 100644 --- a/libraries/stdlib/js/src/kotlin/numbers.kt +++ b/libraries/stdlib/js/src/kotlin/numbers.kt @@ -35,4 +35,184 @@ public actual fun Double.isFinite(): Boolean = !isInfinite() && !isNaN() /** * Returns `true` if the argument is a finite floating-point value; returns `false` otherwise (for `NaN` and infinity arguments). */ -public actual fun Float.isFinite(): Boolean = !isInfinite() && !isNaN() \ No newline at end of file +public actual fun Float.isFinite(): Boolean = !isInfinite() && !isNaN() + + +/** + * Counts the number of set bits in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.countOneBits(): Int { + // Hacker's Delight 5-1 algorithm + var v = this + v = (v and 0x55555555) + (v.ushr(1) and 0x55555555) + v = (v and 0x33333333) + (v.ushr(2) and 0x33333333) + v = (v and 0x0F0F0F0F) + (v.ushr(4) and 0x0F0F0F0F) + v = (v and 0x00FF00FF) + (v.ushr(8) and 0x00FF00FF) + v = (v and 0x0000FFFF) + (v.ushr(16)) + return v +} + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@Suppress("DEPRECATION") +@kotlin.internal.InlineOnly +public actual inline fun Int.countLeadingZeroBits(): Int = kotlin.js.Math.clz32(this) + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.countTrailingZeroBits(): Int = + // Hacker's Delight 5-4 algorithm for expressing countTrailingZeroBits with countLeadingZeroBits + Int.SIZE_BITS - (this or -this).inv().countLeadingZeroBits() + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.takeHighestOneBit(): Int = + if (this == 0) 0 else 1.shl(Int.SIZE_BITS - 1 - countLeadingZeroBits()) + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.takeLowestOneBit(): Int = + // Hacker's Delight 2-1 algorithm for isolating rightmost 1-bit + this and -this + +/** + * Rotates the binary representation of this [Int] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.rotateLeft(bitCount: Int): Int = + shl(bitCount) or ushr(Int.SIZE_BITS - bitCount) + + +/** + * Rotates the binary representation of this [Int] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Int.rotateRight(bitCount: Int): Int = + shl(Int.SIZE_BITS - bitCount) or ushr(bitCount) + + +/** + * Counts the number of set bits in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.countOneBits(): Int = + high.countOneBits() + low.countOneBits() + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.countLeadingZeroBits(): Int = + when (val high = this.high) { + 0 -> Int.SIZE_BITS + low.countLeadingZeroBits() + else -> high.countLeadingZeroBits() + } + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.countTrailingZeroBits(): Int = + when (val low = this.low) { + 0 -> Int.SIZE_BITS + high.countTrailingZeroBits() + else -> low.countTrailingZeroBits() + } + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.takeHighestOneBit(): Long = + when (val high = this.high) { + 0 -> Long(low.takeHighestOneBit(), 0) + else -> Long(0, high.takeHighestOneBit()) + } + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.takeLowestOneBit(): Long = + when (val low = this.low) { + 0 -> Long(0, high.takeLowestOneBit()) + else -> Long(low.takeLowestOneBit(), 0) + } + +/** + * Rotates the binary representation of this [Long] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public actual fun Long.rotateLeft(bitCount: Int): Long { + if ((bitCount and 31) != 0) { + val low = this.low + val high = this.high + val newLow = low.shl(bitCount) or high.ushr(-bitCount) + val newHigh = high.shl(bitCount) or low.ushr(-bitCount) + return if ((bitCount and 32) == 0) Long(newLow, newHigh) else Long(newHigh, newLow) + } else { + return if ((bitCount and 32) == 0) this else Long(high, low) + } +} + + +/** + * Rotates the binary representation of this [Long] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.rotateRight(bitCount: Int): Long = rotateLeft(-bitCount) diff --git a/libraries/stdlib/jvm/src/kotlin/util/NumbersJVM.kt b/libraries/stdlib/jvm/src/kotlin/util/NumbersJVM.kt index ec7731e0199..7ad9d9d67bb 100644 --- a/libraries/stdlib/jvm/src/kotlin/util/NumbersJVM.kt +++ b/libraries/stdlib/jvm/src/kotlin/util/NumbersJVM.kt @@ -92,3 +92,152 @@ public actual inline fun Float.toRawBits(): Int = java.lang.Float.floatToRawIntB @SinceKotlin("1.2") @kotlin.internal.InlineOnly public actual inline fun Float.Companion.fromBits(bits: Int): Float = java.lang.Float.intBitsToFloat(bits) + + +/** + * Counts the number of set bits in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.countOneBits(): Int = Integer.bitCount(this) + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.countLeadingZeroBits(): Int = Integer.numberOfLeadingZeros(this) + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.countTrailingZeroBits(): Int = Integer.numberOfTrailingZeros(this) + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.takeHighestOneBit(): Int = Integer.highestOneBit(this) + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.takeLowestOneBit(): Int = Integer.lowestOneBit(this) + +/** + * Rotates the binary representation of this [Int] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.rotateLeft(bitCount: Int): Int = Integer.rotateLeft(this, bitCount) + + +/** + * Rotates the binary representation of this [Int] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Int.rotateRight(bitCount: Int): Int = Integer.rotateRight(this, bitCount) + + +/** + * Counts the number of set bits in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.countOneBits(): Int = java.lang.Long.bitCount(this) + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.countLeadingZeroBits(): Int = java.lang.Long.numberOfLeadingZeros(this) + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.countTrailingZeroBits(): Int = java.lang.Long.numberOfTrailingZeros(this) + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.takeHighestOneBit(): Long = java.lang.Long.highestOneBit(this) + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.takeLowestOneBit(): Long = java.lang.Long.lowestOneBit(this) + +/** + * Rotates the binary representation of this [Long] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.rotateLeft(bitCount: Int): Long = java.lang.Long.rotateLeft(this, bitCount) + +/** + * Rotates the binary representation of this [Long] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public actual inline fun Long.rotateRight(bitCount: Int): Long = java.lang.Long.rotateRight(this, bitCount) + + diff --git a/libraries/stdlib/src/kotlin/util/Numbers.kt b/libraries/stdlib/src/kotlin/util/Numbers.kt new file mode 100644 index 00000000000..ffbbef7b476 --- /dev/null +++ b/libraries/stdlib/src/kotlin/util/Numbers.kt @@ -0,0 +1,287 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:kotlin.jvm.JvmMultifileClass +@file:kotlin.jvm.JvmName("NumbersKt") +package kotlin + +/** + * Counts the number of set bits in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.countOneBits(): Int + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.countLeadingZeroBits(): Int + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Int] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.countTrailingZeroBits(): Int + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.takeHighestOneBit(): Int + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Int] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.takeLowestOneBit(): Int + +/** + * Rotates the binary representation of this [Int] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.rotateLeft(bitCount: Int): Int + + +/** + * Rotates the binary representation of this [Int] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Int.SIZE_BITS] (32) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 32)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Int.rotateRight(bitCount: Int): Int + + +/** + * Counts the number of set bits in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.countOneBits(): Int + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.countLeadingZeroBits(): Int + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Long] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.countTrailingZeroBits(): Int + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.takeHighestOneBit(): Long + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Long] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.takeLowestOneBit(): Long + +/** + * Rotates the binary representation of this [Long] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.rotateLeft(bitCount: Int): Long + +/** + * Rotates the binary representation of this [Long] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Long.SIZE_BITS] (64) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 64)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public expect fun Long.rotateRight(bitCount: Int): Long + +/** + * Counts the number of set bits in the binary representation of this [Byte] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Byte.countOneBits(): Int = (toInt() and 0xFF).countOneBits() + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Byte] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Byte.countLeadingZeroBits(): Int = (toInt() and 0xFF).countLeadingZeroBits() - (Int.SIZE_BITS - Byte.SIZE_BITS) + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Byte] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Byte.countTrailingZeroBits(): Int = (toInt() or 0x100).countTrailingZeroBits() + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Byte] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Byte.takeHighestOneBit(): Byte = (toInt() and 0xFF).takeHighestOneBit().toByte() + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Byte] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Byte.takeLowestOneBit(): Byte = toInt().takeLowestOneBit().toByte() + + +/** + * Rotates the binary representation of this [Byte] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Byte.SIZE_BITS] (8) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 8)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun Byte.rotateLeft(bitCount: Int): Byte = + (toInt().shl(bitCount and 7) or (toInt() and 0xFF).ushr(8 - (bitCount and 7))).toByte() + +/** + * Rotates the binary representation of this [Byte] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Byte.SIZE_BITS] (8) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 8)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun Byte.rotateRight(bitCount: Int): Byte = + (toInt().shl(8 - (bitCount and 7)) or (toInt() and 0xFF).ushr(bitCount and 7)).toByte() + +/** + * Counts the number of set bits in the binary representation of this [Short] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Short.countOneBits(): Int = (toInt() and 0xFFFF).countOneBits() + +/** + * Counts the number of consecutive most significant bits that are zero in the binary representation of this [Short] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Short.countLeadingZeroBits(): Int = + (toInt() and 0xFFFF).countLeadingZeroBits() - (Int.SIZE_BITS - Short.SIZE_BITS) + +/** + * Counts the number of consecutive least significant bits that are zero in the binary representation of this [Short] number. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Short.countTrailingZeroBits(): Int = (toInt() or 0x10000).countTrailingZeroBits() + +/** + * Returns a number having a single bit set in the position of the most significant set bit of this [Short] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Short.takeHighestOneBit(): Short = (toInt() and 0xFFFF).takeHighestOneBit().toShort() + +/** + * Returns a number having a single bit set in the position of the least significant set bit of this [Short] number, + * or zero, if this number is zero. + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +@kotlin.internal.InlineOnly +public inline fun Short.takeLowestOneBit(): Short = toInt().takeLowestOneBit().toShort() + + +/** + * Rotates the binary representation of this [Short] number left by the specified [bitCount] number of bits. + * The most significant bits pushed out from the left side reenter the number as the least significant bits on the right side. + * + * Rotating the number left by a negative bit count is the same as rotating it right by the negated bit count: + * `number.rotateLeft(-n) == number.rotateRight(n)` + * + * Rotating by a multiple of [Short.SIZE_BITS] (16) returns the same number, or more generally + * `number.rotateLeft(n) == number.rotateLeft(n % 16)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun Short.rotateLeft(bitCount: Int): Short = + (toInt().shl(bitCount and 15) or (toInt() and 0xFFFF).ushr(16 - (bitCount and 15))).toShort() + +/** + * Rotates the binary representation of this [Short] number right by the specified [bitCount] number of bits. + * The least significant bits pushed out from the right side reenter the number as the most significant bits on the left side. + * + * Rotating the number right by a negative bit count is the same as rotating it left by the negated bit count: + * `number.rotateRight(-n) == number.rotateLeft(n)` + * + * Rotating by a multiple of [Short.SIZE_BITS] (16) returns the same number, or more generally + * `number.rotateRight(n) == number.rotateRight(n % 16)` + */ +@SinceKotlin("1.3") +@ExperimentalStdlibApi +public fun Short.rotateRight(bitCount: Int): Short = + (toInt().shl(16 - (bitCount and 15)) or (toInt() and 0xFFFF).ushr(bitCount and 15)).toShort() diff --git a/libraries/stdlib/test/numbers/NumbersTest.kt b/libraries/stdlib/test/numbers/NumbersTest.kt index 51ef118a1da..5a5f862629b 100644 --- a/libraries/stdlib/test/numbers/NumbersTest.kt +++ b/libraries/stdlib/test/numbers/NumbersTest.kt @@ -5,6 +5,7 @@ package test.numbers +import kotlin.random.Random import kotlin.test.* object NumbersTestConstants { @@ -199,6 +200,196 @@ class NumbersTest { testSizes(UShort, UShort.SIZE_BYTES, UShort.SIZE_BITS, 2) testSizes(UInt, UInt.SIZE_BYTES, UInt.SIZE_BITS, 4) testSizes(ULong, ULong.SIZE_BYTES, ULong.SIZE_BITS, 8) - } + } + + @Test + fun byteBits() { + fun test(value: Byte, oneBits: Int, leadingZeroes: Int, trailingZeroes: Int) { + assertEquals(oneBits, value.countOneBits()) + assertEquals(leadingZeroes, value.countLeadingZeroBits()) + assertEquals(trailingZeroes, value.countTrailingZeroBits()) + val highestBit = if (leadingZeroes < Byte.SIZE_BITS) 1.shl(Byte.SIZE_BITS - leadingZeroes - 1).toByte() else 0 + val lowestBit = if (trailingZeroes < Byte.SIZE_BITS) 1.shl(trailingZeroes).toByte() else 0 + assertEquals(highestBit, value.takeHighestOneBit()) + assertEquals(lowestBit, value.takeLowestOneBit()) + } + + test(0, 0, 8, 8) + test(1, 1, 7, 0) + test(2, 1, 6, 1) + test(0x44, 2, 1, 2) + test(0x80.toByte(), 1, 0, 7) + test(0xF0.toByte(), 4, 0, 4) + } + + @Test + fun shortBits() { + fun test(value: Short, oneBits: Int, leadingZeroes: Int, trailingZeroes: Int) { + assertEquals(oneBits, value.countOneBits()) + assertEquals(leadingZeroes, value.countLeadingZeroBits()) + assertEquals(trailingZeroes, value.countTrailingZeroBits()) + val highestBit = if (leadingZeroes < Short.SIZE_BITS) 1.shl(Short.SIZE_BITS - leadingZeroes - 1).toShort() else 0 + val lowestBit = if (trailingZeroes < Short.SIZE_BITS) 1.shl(trailingZeroes).toShort() else 0 + assertEquals(highestBit, value.takeHighestOneBit()) + assertEquals(lowestBit, value.takeLowestOneBit()) + } + + test(0, 0, 16, 16) + test(1, 1, 15, 0) + test(2, 1, 14, 1) + test(0xF2, 5, 8, 1) + test(0x8000.toShort(), 1, 0, 15) + test(0xF200.toShort(), 5, 0, 9) + } + + @Test + fun intBits() { + fun test(value: Int, oneBits: Int, leadingZeroes: Int, trailingZeroes: Int) { + assertEquals(oneBits, value.countOneBits()) + assertEquals(leadingZeroes, value.countLeadingZeroBits()) + assertEquals(trailingZeroes, value.countTrailingZeroBits()) + val highestBit = if (leadingZeroes < Int.SIZE_BITS) 1.shl(Int.SIZE_BITS - leadingZeroes - 1) else 0 + val lowestBit = if (trailingZeroes < Int.SIZE_BITS) 1.shl(trailingZeroes) else 0 + assertEquals(highestBit, value.takeHighestOneBit()) + assertEquals(lowestBit, value.takeLowestOneBit()) + } + + test(0, 0, 32, 32) + test(1, 1, 31, 0) + test(2, 1, 30, 1) + test(0xF002, 5, 16, 1) + test(0xF00F0000.toInt(), 8, 0, 16) + } + + @Test + fun longBits() { + fun test(value: Long, oneBits: Int, leadingZeroes: Int, trailingZeroes: Int) { + assertEquals(oneBits, value.countOneBits()) + assertEquals(leadingZeroes, value.countLeadingZeroBits()) + assertEquals(trailingZeroes, value.countTrailingZeroBits()) + val highestBit = if (leadingZeroes < Long.SIZE_BITS) 1L.shl(Long.SIZE_BITS - leadingZeroes - 1).toLong() else 0 + val lowestBit = if (trailingZeroes < Long.SIZE_BITS) 1L.shl(trailingZeroes).toLong() else 0 + assertEquals(highestBit, value.takeHighestOneBit()) + assertEquals(lowestBit, value.takeLowestOneBit()) + } + + test(0, 0, 64, 64) + test(1, 1, 63, 0) + test(2, 1, 62, 1) + test(0xF002, 5, 48, 1) + test(0xF00F0000L, 8, 32, 16) + test(0x1111_3333_EEEE_0000L, 4 + 8 + 12, 3, 17) + } + + + @Test + fun intRotate() { + fun test(value: Int, n: Int, expected: Int) { + assertEquals(expected, value.rotateLeft(n)) + assertEquals(expected, value.rotateRight(-n)) + } + + fun testCyclic(value: Int) { + for (n in -2 * Int.SIZE_BITS..2 * Int.SIZE_BITS) { + val rl = value.rotateLeft(n) + val rr = value.rotateRight(-n) + assertEquals(rl, rr) + assertEquals(rl, value.rotateLeft(n % Int.SIZE_BITS)) + assertEquals(rr, value.rotateRight((-n) % Int.SIZE_BITS)) + assertEquals(value, value.rotateLeft(n).rotateLeft(-n)) + assertEquals(value, value.rotateRight(n).rotateRight(-n)) + } + } + + test(0x7_3422345, 4, 0x3422345_7) + test(0x7342234_5, -4, 0x5_7342234) + test(0x73422345, 1, 0xE684468A.toInt()) + repeat(100) { + testCyclic(Random.nextInt()) + } + } + + @Test + fun byteRotate() { + fun test(value: Byte, n: Int, expected: Byte) { + assertEquals(expected, value.rotateLeft(n)) + assertEquals(expected, value.rotateRight(-n)) + } + + fun testCyclic(value: Byte) { + for (n in -2 * Byte.SIZE_BITS..2 * Byte.SIZE_BITS) { + val rl = value.rotateLeft(n) + val rr = value.rotateRight(-n) + assertEquals(rl, rr) + assertEquals(rl, value.rotateLeft(n % Byte.SIZE_BITS)) + assertEquals(rr, value.rotateRight((-n) % Byte.SIZE_BITS)) + assertEquals(value, value.rotateLeft(n).rotateLeft(-n)) + assertEquals(value, value.rotateRight(n).rotateRight(-n)) + } + } + + test(0x73, 4, 0x37) + test(0x73, -3, 0x6E) + test(0x73, 1, 0xE6.toByte()) + test(0xE6.toByte(), 1, 0xCD.toByte()) + repeat(100) { + testCyclic(Random.nextInt().toByte()) + } + } + + @Test + fun longRotate() { + fun test(value: Long, n: Int, expected: Long) { + assertEquals(expected, value.rotateLeft(n)) + assertEquals(expected, value.rotateRight(-n)) + } + + fun testCyclic(value: Long) { + for (n in -2 * Long.SIZE_BITS..2 * Long.SIZE_BITS) { + val rl = value.rotateLeft(n) + val rr = value.rotateRight(-n) + assertEquals(rl, rr) + assertEquals(rl, value.rotateLeft(n % Long.SIZE_BITS)) + assertEquals(rr, value.rotateRight((-n) % Long.SIZE_BITS)) + assertEquals(value, value.rotateLeft(n).rotateLeft(-n)) + assertEquals(value, value.rotateRight(n).rotateRight(-n)) + } + } + + test(0x7372ABAC_DEEF0123, 4, 0x372ABAC_DEEF01237) + test(0x88888888_44444444U.toLong(), -3, 0x91111111_08888888u.toLong()) + test(0x88888888_44444444U.toLong(), 1, 0x11111110_88888889) + repeat(100) { + testCyclic(Random.nextLong()) + } + } + + @Test + fun shortRotate() { + fun test(value: Short, n: Int, expected: Short) { + assertEquals(expected, value.rotateLeft(n)) + assertEquals(expected, value.rotateRight(-n)) + } + + fun testCyclic(value: Short) { + for (n in -2 * Short.SIZE_BITS..2 * Short.SIZE_BITS) { + val rl = value.rotateLeft(n) + val rr = value.rotateRight(-n) + assertEquals(rl, rr) + assertEquals(rl, value.rotateLeft(n % Short.SIZE_BITS)) + assertEquals(rr, value.rotateRight((-n) % Short.SIZE_BITS)) + assertEquals(value, value.rotateLeft(n).rotateLeft(-n)) + assertEquals(value, value.rotateRight(n).rotateRight(-n)) + } + } + + test(0x7361, 4, 0x3617) + test(0x7361, -3, 0b001_0111_0011_0110_0) + test(0x7361, 1, 0b111_0011_0110_0001_0.toShort()) + test(0xE6C2.toShort(), 1, 0b11_0011_0110_0001_01.toShort()) + repeat(100) { + testCyclic(Random.nextInt().toShort()) + } + } } \ No newline at end of file diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 0517ef21a51..d210107d59e 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -119,6 +119,13 @@ public final class kotlin/NotImplementedError : java/lang/Error { public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } +public final class kotlin/NumbersKt { + public static final fun rotateLeft (BI)B + public static final fun rotateLeft (SI)S + public static final fun rotateRight (BI)B + public static final fun rotateRight (SI)S +} + public abstract interface annotation class kotlin/OptionalExpectation : java/lang/annotation/Annotation { }