diff --git a/js/js.libraries/src/core/kotlin.math.kt b/js/js.libraries/src/core/kotlin.math.kt index 713bb377dee..54a692bc95b 100644 --- a/js/js.libraries/src/core/kotlin.math.kt +++ b/js/js.libraries/src/core/kotlin.math.kt @@ -313,10 +313,30 @@ public fun round(a: Double): Double { return if (floor % 2 == 0.0) floor else ceil(a) } -// also as extension val [absoluteValue] -inline fun abs(a: Double): Double = nativeMath.abs(a) -// also as extension val [sign] -inline fun sgn(a: Double): Double = nativeMath.sign(a) +/** + * Returns the absolute value of the given value [a]. + * + * Special cases: + * - `abs(NaN)` is `NaN` + * + * @see absoluteValue extension property for [Double] + */ +@InlineOnly +public inline fun abs(a: Double): Double = nativeMath.abs(a) + +/** + * Returns the sign of the given value [a]: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `sign(NaN)` is `NaN` + */ +@InlineOnly +public inline fun sign(a: Double): Double = nativeMath.sign(a) + + /** * Returns the smaller of two values. * @@ -337,12 +357,45 @@ public inline fun max(a: Double, b: Double): Double = nativeMath.max(a, b) inline fun Double.pow(other: Double): Double = nativeMath.pow(this, other) inline fun Double.pow(other: Int): Double = nativeMath.pow(this, other.toDouble()) -inline val Double.absoluteValue: Double get() = nativeMath.abs(this) -inline val Double.sign: Double get() = nativeMath.sign(this) +/** + * Returns the absolute value of this value. + * + * Special cases: + * - `NaN.absoluteValue` is `NaN` + * + * @see abs function + */ +@InlineOnly +public inline val Double.absoluteValue: Double get() = nativeMath.abs(this) -// TODO: Reimplement here -fun Double.withSign(sign: Double): Double = this.absoluteValue * sign.sign -inline fun Double.withSign(sign: Int): Double = this.withSign(sign.toDouble()) +/** + * Returns the sign of this value: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `NaN.sign` is `NaN` + */ +@InlineOnly +public inline val Double.sign: Double get() = nativeMath.sign(this) + +/** + * Returns this value with the sign bit same as of the [sign] value. + * + * If [sign] is `NaN` the sign of the result is undefined. + */ +public fun Double.withSign(sign: Double): Double = + this.absoluteValue * when(sign) { + 0.0 -> sign(1 / sign) + else -> sign(sign) + } + +/** + * Returns this value with the sign bit same as of the [sign] value. + */ +@InlineOnly +public 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]. diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index b143e0e2599..f18275856c4 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -317,13 +317,25 @@ public inline fun round(a: Double): Double = nativeMath.rint(a) /** * Returns the absolute value of the given value [a]. * + * Special cases: + * - `abs(NaN)` is `NaN` + * * @see absoluteValue extension property for [Double] */ @InlineOnly public inline fun abs(a: Double): Double = nativeMath.abs(a) -// also as extension val [sign] + +/** + * Returns the sign of the given value [a]: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `sign(NaN)` is `NaN` + */ @InlineOnly -public inline fun sgn(a: Double): Double = nativeMath.signum(a) +public inline fun sign(a: Double): Double = nativeMath.signum(a) @@ -364,10 +376,41 @@ public inline fun Double.pow(other: Int): Double = nativeMath.pow(this, other.to public inline fun Double.IEEErem(other: Double): Double = nativeMath.IEEEremainder(this, other) + +/** + * Returns the absolute value of this value. + * + * Special cases: + * - `NaN.absoluteValue` is `NaN` + * + * @see abs function + */ +@InlineOnly public inline val Double.absoluteValue: Double get() = nativeMath.abs(this) + +/** + * Returns the sign of this value: + * - `-1.0` if the value is negative, + * - zero if the value is zero, + * - `1.0` if the value is positive + * + * Special case: + * - `NaN.sign` is `NaN` + */ +@InlineOnly public inline val Double.sign: Double get() = nativeMath.signum(this) +/** + * Returns this value with the sign bit same as of the [sign] value. + * + * If [sign] is `NaN` the sign of the result is undefined. + */ +@InlineOnly public inline fun Double.withSign(sign: Double): Double = nativeMath.copySign(this, sign) +/** + * Returns this value with the sign bit same as of the [sign] value. + */ +@InlineOnly public inline fun Double.withSign(sign: Int): Double = nativeMath.copySign(this, sign.toDouble()) public inline val Double.ulp: Double get() = nativeMath.ulp(this) diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index 5db882daece..d78298c75de 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -207,5 +207,62 @@ class DoubleMathTest { assertFails { Double.NaN.roundToInt() } } + @Test fun absoluteValue() { + assertTrue(abs(Double.NaN).isNaN()) + assertTrue(Double.NaN.absoluteValue.isNaN()) + + for (value in listOf(0.0, Double.MIN_VALUE, 0.1, 1.0, 1000.0, Double.MAX_VALUE, Double.POSITIVE_INFINITY)) { + assertEquals(value, value.absoluteValue) + assertEquals(value, (-value).absoluteValue) + assertEquals(value, abs(value)) + assertEquals(value, abs(-value)) + } + } + + @Test fun signs() { + assertTrue(sign(Double.NaN).isNaN()) + assertTrue(Double.NaN.sign.isNaN()) + + val negatives = listOf(Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, -1.0, -Double.MIN_VALUE) + for (value in negatives) { + assertEquals(-1.0, sign(value)) + assertEquals(-1.0, value.sign) + } + + val zeroes = listOf(0.0, -0.0) + for (value in zeroes) { + assertEquals(value, sign(value)) + assertEquals(value, value.sign) + } + + + val positives = listOf(Double.POSITIVE_INFINITY, Double.MAX_VALUE, 1.0, Double.MIN_VALUE) + for (value in positives) { + assertEquals(1.0, sign(value)) + assertEquals(1.0, value.sign) + } + + val allValues = negatives + positives + for (a in allValues) { + for (b in allValues) { + val r = a.withSign(b) + assertEquals(a.absoluteValue, r.absoluteValue) + assertEquals(b.sign, r.sign) + } + + val rp0 = a.withSign(0.0) + assertEquals(1.0, rp0.sign) + assertEquals(a.absoluteValue, rp0.absoluteValue) + + val rm0 = a.withSign(-0.0) + assertEquals(-1.0, rm0.sign) + assertEquals(a.absoluteValue, rm0.absoluteValue) + + val ri = a.withSign(-1) + assertEquals(-1.0, ri.sign) + assertEquals(a.absoluteValue, ri.absoluteValue) + } + } + }