diff --git a/js/js.libraries/src/core/kotlin.math.kt b/js/js.libraries/src/core/kotlin.math.kt index 2a8aed2821e..713bb377dee 100644 --- a/js/js.libraries/src/core/kotlin.math.kt +++ b/js/js.libraries/src/core/kotlin.math.kt @@ -266,10 +266,52 @@ public inline fun log2(a: Double): Double = nativeMath.log2(a) @InlineOnly public inline fun log1p(a: Double): Double = nativeMath.log1p(a) +/** + * Rounds the given value [a] to an integer towards positive infinity. -inline fun ceil(a: Double): Double = nativeMath.ceil(a).unsafeCast() // TODO: Remove unsafe cast after removing public js.math -inline fun floor(a: Double): Double = nativeMath.floor(a).unsafeCast() -inline fun truncate(a: Double): Double = nativeMath.trunc(a) // polyfill + * @return the smallest double value that is greater than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `ceil(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun ceil(a: Double): Double = nativeMath.ceil(a).unsafeCast() // TODO: Remove unsafe cast after removing public js.math + +/** + * Rounds the given value [a] to an integer towards negative infinity. + + * @return the largest double value that is smaller than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `floor(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun floor(a: Double): Double = nativeMath.floor(a).unsafeCast() + +/** + * Rounds the given value [a] to an integer towards zero. + * + * @return the value [a] having its fractional part truncated. + * + * Special cases: + * - `truncate(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun truncate(a: Double): Double = nativeMath.trunc(a) + +/** + * Rounds the given value [a] towards the closest integer with ties rounded towards even integer. + * + * Special cases: + * - `round(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +public fun round(a: Double): Double { + if (a % 0.5 != 0.0) { + return nativeMath.round(a).unsafeCast() + } + val floor = floor(a) + return if (floor % 2 == 0.0) floor else ceil(a) +} // also as extension val [absoluteValue] inline fun abs(a: Double): Double = nativeMath.abs(a) @@ -302,8 +344,39 @@ inline val Double.sign: Double get() = nativeMath.sign(this) fun Double.withSign(sign: Double): Double = this.absoluteValue * sign.sign inline fun Double.withSign(sign: Int): Double = this.withSign(sign.toDouble()) +/** + * Rounds this [Double] value to the nearest integer and converts the result to [Int]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToInt() == Int.MAX_VALUE` when `x > Int.MAX_VALUE` + * - `x.roundToInt() == Int.MIN_VALUE` when `x < Int.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ +public fun Double.roundToInt(): Int = when { + isNaN() -> throw IllegalArgumentException("Cannot round NaN value.") + this > Int.MAX_VALUE -> Int.MAX_VALUE + this < Int.MIN_VALUE -> Int.MIN_VALUE + else -> nativeMath.round(this).unsafeCast().toInt() +} -fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this).unsafeCast().toLong() +/** + * Rounds this [Double] value to the nearest integer and converts the result to [Long]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToLong() == Long.MAX_VALUE` when `x > Long.MAX_VALUE` + * - `x.roundToLong() == Long.MIN_VALUE` when `x < Long.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ +public fun Double.roundToLong(): Long = when { + isNaN() -> throw IllegalArgumentException("Cannot round NaN value.") + this > Long.MAX_VALUE -> Long.MAX_VALUE + this < Long.MIN_VALUE -> Long.MIN_VALUE + else -> nativeMath.round(this).unsafeCast().toLong() +} @@ -325,7 +398,7 @@ inline fun Float.withSign(sign: Float): Float = this.toDouble().withSign(sign.to inline fun Float.withSign(sign: Int): Float = this.toDouble().withSign(sign.toDouble()).toFloat() -fun Float.roundToInt(): Int = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) +fun Float.roundToInt(): Int = toDouble().roundToInt() fun Float.roundToLong(): Long = toDouble().roundToLong() diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index b91db6d6323..b143e0e2599 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -268,13 +268,61 @@ public fun log2(a: Double): Double = nativeMath.log(a) / LN2 @InlineOnly public inline fun log1p(a: Double): Double = nativeMath.log1p(a) -public inline fun ceil(a: Double): Double = nativeMath.ceil(a) -public inline fun floor(a: Double): Double = nativeMath.floor(a) -public inline fun truncate(a: Double): Double = nativeMath.rint(a) +/** + * Rounds the given value [a] to an integer towards positive infinity. -// also as extension val [absoluteValue] + * @return the smallest double value that is greater than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `ceil(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun ceil(a: Double): Double = nativeMath.ceil(a) + +/** + * Rounds the given value [a] to an integer towards negative infinity. + + * @return the largest double value that is smaller than the given value [a] and is a mathematical integer. + * + * Special cases: + * - `floor(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun floor(a: Double): Double = nativeMath.floor(a) + +/** + * Rounds the given value [a] to an integer towards zero. + * + * @return the value [a] having its fractional part truncated. + * + * Special cases: + * - `truncate(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +public fun truncate(a: Double): Double = when { + a.isNaN() || a.isInfinite() -> a + a > 0 -> floor(a) + else -> ceil(a) +} + +/** + * Rounds the given value [a] towards the closest integer with ties rounded towards even integer. + * + * Special cases: + * - `round(x)` is `x` where `x` is `NaN` or `+Inf` or `-Inf` or already a mathematical integer. + */ +@InlineOnly +public inline fun round(a: Double): Double = nativeMath.rint(a) + + +/** + * Returns the absolute value of the given value [a]. + * + * @see absoluteValue extension property for [Double] + */ +@InlineOnly public inline fun abs(a: Double): Double = nativeMath.abs(a) // also as extension val [sign] +@InlineOnly public inline fun sgn(a: Double): Double = nativeMath.signum(a) @@ -327,6 +375,33 @@ public inline fun Double.nextUp(): Double = nativeMath.nextUp(this) public inline fun Double.nextDown(): Double = nativeMath.nextAfter(this, Double.NEGATIVE_INFINITY) public inline fun Double.nextTowards(to: Double): Double = nativeMath.nextAfter(this, to) +/** + * Rounds this [Double] value to the nearest integer and converts the result to [Int]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToInt() == Int.MAX_VALUE` when `x > Int.MAX_VALUE` + * - `x.roundToInt() == Int.MIN_VALUE` when `x < Int.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ +public fun Double.roundToInt(): Int = when { + isNaN() -> throw IllegalArgumentException("Cannot round NaN value.") + this > Int.MAX_VALUE -> Int.MAX_VALUE + this < Int.MIN_VALUE -> Int.MIN_VALUE + else -> nativeMath.round(this).toInt() +} + +/** + * Rounds this [Double] value to the nearest integer and converts the result to [Long]. + * Ties are rounded towards positive infinity. + * + * Special cases: + * - `x.roundToLong() == Long.MAX_VALUE` when `x > Long.MAX_VALUE` + * - `x.roundToLong() == Long.MIN_VALUE` when `x < Long.MIN_VALUE` + * + * @throws IllegalArgumentException when this value is `NaN` + */ public fun Double.roundToLong(): Long = if (isNaN()) throw IllegalArgumentException("Cannot round NaN value.") else nativeMath.round(this) diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index 29ee187ecbf..5db882daece 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -148,9 +148,64 @@ class DoubleMathTest { } @Test fun rounding() { - TODO() + for (value in listOf(Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 0.0, 1.0, -10.0)) { + assertEquals(value, ceil(value)) + assertEquals(value, floor(value)) + assertEquals(value, truncate(value)) + assertEquals(value, round(value)) + } + val data = arrayOf( // v floor trunc round ceil + doubleArrayOf( 1.3, 1.0, 1.0, 1.0, 2.0), + doubleArrayOf(-1.3, -2.0, -1.0, -1.0, -1.0), + doubleArrayOf( 1.5, 1.0, 1.0, 2.0, 2.0), + doubleArrayOf(-1.5, -2.0, -1.0, -2.0, -1.0), + doubleArrayOf( 1.8, 1.0, 1.0, 2.0, 2.0), + doubleArrayOf(-1.8, -2.0, -1.0, -2.0, -1.0), + + doubleArrayOf( 2.3, 2.0, 2.0, 2.0, 3.0), + doubleArrayOf(-2.3, -3.0, -2.0, -2.0, -2.0), + doubleArrayOf( 2.5, 2.0, 2.0, 2.0, 3.0), + doubleArrayOf(-2.5, -3.0, -2.0, -2.0, -2.0), + doubleArrayOf( 2.8, 2.0, 2.0, 3.0, 3.0), + doubleArrayOf(-2.8, -3.0, -2.0, -3.0, -2.0) + ) + for ((v, f, t, r, c) in data) { + assertEquals(f, floor(v), "floor($v)") + assertEquals(t, truncate(v), "truncate($v)") + assertEquals(r, round(v), "round($v)") + assertEquals(c, ceil(v), "ceil($v)") + } } + @Test fun roundingConversion() { + assertEquals(1L, 1.0.roundToLong()) + assertEquals(1L, 1.1.roundToLong()) + assertEquals(2L, 1.5.roundToLong()) + assertEquals(3L, 2.5.roundToLong()) + assertEquals(-2L, (-2.5).roundToLong()) + assertEquals(-3L, (-2.6).roundToLong()) + assertEquals(9223372036854774784, (9223372036854774800.0).roundToLong()) + assertEquals(Long.MAX_VALUE, Double.MAX_VALUE.roundToLong()) + assertEquals(Long.MIN_VALUE, (-Double.MAX_VALUE).roundToLong()) + assertEquals(Long.MAX_VALUE, Double.POSITIVE_INFINITY.roundToLong()) + assertEquals(Long.MIN_VALUE, Double.NEGATIVE_INFINITY.roundToLong()) + + assertFails { Double.NaN.roundToLong() } + + assertEquals(1, 1.0.roundToInt()) + assertEquals(1, 1.1.roundToInt()) + assertEquals(2, 1.5.roundToInt()) + assertEquals(3, 2.5.roundToInt()) + assertEquals(-2, (-2.5).roundToInt()) + assertEquals(-3, (-2.6).roundToInt()) + assertEquals(2123456789, (2123456789.0).roundToInt()) + assertEquals(Int.MAX_VALUE, Double.MAX_VALUE.roundToInt()) + assertEquals(Int.MIN_VALUE, (-Double.MAX_VALUE).roundToInt()) + assertEquals(Int.MAX_VALUE, Double.POSITIVE_INFINITY.roundToInt()) + assertEquals(Int.MIN_VALUE, Double.NEGATIVE_INFINITY.roundToInt()) + + assertFails { Double.NaN.roundToInt() } + } }