From aa3bfc55c33b06cfdddbe72bb3bb8d739e384b49 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Thu, 31 Aug 2017 22:12:08 +0300 Subject: [PATCH] Implement in JS nextUp(), nextDown(), nextTowards() and ulp extensions #KT-4900 --- js/js.libraries/src/core/math.kt | 56 ++++++++++++++++++++ js/js.libraries/src/js/misc.js | 2 +- libraries/stdlib/common/src/kotlin/MathH.kt | 35 ++++++++++++ libraries/stdlib/src/kotlin/util/MathJVM.kt | 4 +- libraries/stdlib/test/numbers/MathJVMTest.kt | 34 ------------ libraries/stdlib/test/numbers/MathTest.kt | 44 +++++++++++++++ 6 files changed, 138 insertions(+), 37 deletions(-) diff --git a/js/js.libraries/src/core/math.kt b/js/js.libraries/src/core/math.kt index ef4f97213ae..89fb14d269b 100644 --- a/js/js.libraries/src/core/math.kt +++ b/js/js.libraries/src/core/math.kt @@ -476,6 +476,62 @@ public fun Double.withSign(sign: Double): Double { @InlineOnly public inline fun Double.withSign(sign: Int): Double = this.withSign(sign.toDouble()) +/** + * Returns the ulp (unit in the last place) of this value. + * + * An ulp is a positive distance between this value and the next nearest [Double] value larger in magnitude. + * + * Special Cases: + * - `NaN.ulp` is `NaN` + * - `x.ulp` is `+Inf` when `x` is `+Inf` or `-Inf` + * - `0.0.ulp` is `Double.MIN_VALUE` + */ +@SinceKotlin("1.2") +public val Double.ulp: Double get() = when { + this < 0 -> (-this).ulp + this.isNaN() || this == Double.POSITIVE_INFINITY -> this + this == Double.MAX_VALUE -> this - this.nextDown() + else -> this.nextUp() - this +} + +/** + * Returns the [Double] value nearest to this value in direction of positive infinity. + */ +@SinceKotlin("1.2") +public fun Double.nextUp(): Double = when { + this.isNaN() || this == Double.POSITIVE_INFINITY -> this + this == 0.0 -> Double.MIN_VALUE + else -> Double.fromBits(this.toRawBits() + if (this > 0) 1 else -1) +} + +/** + * Returns the [Double] value nearest to this value in direction of negative infinity. + */ +@SinceKotlin("1.2") +public fun Double.nextDown(): Double = when { + this.isNaN() || this == Double.NEGATIVE_INFINITY -> this + this == 0.0 -> -Double.MIN_VALUE + else -> Double.fromBits(this.toRawBits() + if (this > 0) -1 else 1) +} + + +/** + * Returns the [Double] value nearest to this value in direction from this value towards the value [to]. + * + * Special cases: + * - `x.nextTowards(y)` is `NaN` if either `x` or `y` are `NaN` + * - `x.nextTowards(x) == x` + * + */ +@SinceKotlin("1.2") +public fun Double.nextTowards(to: Double): Double = when { + this.isNaN() || to.isNaN() -> Double.NaN + to == this -> to + to > this -> this.nextUp() + else /* to < this */-> this.nextDown() +} + + /** * Rounds this [Double] value to the nearest integer and converts the result to [Int]. * Ties are rounded towards positive infinity. diff --git a/js/js.libraries/src/js/misc.js b/js/js.libraries/src/js/misc.js index c610280ea1b..67ff45d1a46 100644 --- a/js/js.libraries/src/js/misc.js +++ b/js/js.libraries/src/js/misc.js @@ -88,7 +88,7 @@ function imul(a, b) { Kotlin.doubleSignBit = function(value) { bufFloat64[0] = value; return bufInt32[1] & 0x80000000; - } + }; })(); diff --git a/libraries/stdlib/common/src/kotlin/MathH.kt b/libraries/stdlib/common/src/kotlin/MathH.kt index b47642c2cd9..5cc18f12474 100644 --- a/libraries/stdlib/common/src/kotlin/MathH.kt +++ b/libraries/stdlib/common/src/kotlin/MathH.kt @@ -428,6 +428,41 @@ public expect fun Double.withSign(sign: Double): Double @SinceKotlin("1.2") public expect fun Double.withSign(sign: Int): Double +/** + * Returns the ulp (unit in the last place) of this value. + * + * An ulp is a positive distance between this value and the next nearest [Double] value larger in magnitude. + * + * Special Cases: + * - `NaN.ulp` is `NaN` + * - `x.ulp` is `+Inf` when `x` is `+Inf` or `-Inf` + * - `0.0.ulp` is `Double.MIN_VALUE` + */ +@SinceKotlin("1.2") +public expect val Double.ulp: Double + +/** + * Returns the [Double] value nearest to this value in direction of positive infinity. + */ +@SinceKotlin("1.2") +public expect fun Double.nextUp(): Double +/** + * Returns the [Double] value nearest to this value in direction of negative infinity. + */ +@SinceKotlin("1.2") +public expect fun Double.nextDown(): Double + +/** + * Returns the [Double] value nearest to this value in direction from this value towards the value [to]. + * + * Special cases: + * - `x.nextTowards(y)` is `NaN` if either `x` or `y` are `NaN` + * - `x.nextTowards(x) == x` + * + */ +@SinceKotlin("1.2") +public expect fun Double.nextTowards(to: Double): Double + /** * Rounds this [Double] value to the nearest integer and converts the result to [Int]. * Ties are rounded towards positive infinity. diff --git a/libraries/stdlib/src/kotlin/util/MathJVM.kt b/libraries/stdlib/src/kotlin/util/MathJVM.kt index 058630c7e34..481615f6d53 100644 --- a/libraries/stdlib/src/kotlin/util/MathJVM.kt +++ b/libraries/stdlib/src/kotlin/util/MathJVM.kt @@ -555,14 +555,14 @@ public inline fun Double.withSign(sign: Double): Double = nativeMath.copySign(th public inline fun Double.withSign(sign: Int): Double = nativeMath.copySign(this, sign.toDouble()) /** - * Returns the ulp of this value. + * Returns the ulp (unit in the last place) of this value. * * An ulp is a positive distance between this value and the next nearest [Double] value larger in magnitude. * * Special Cases: * - `NaN.ulp` is `NaN` * - `x.ulp` is `+Inf` when `x` is `+Inf` or `-Inf` - * - `0.0.ulp` is `Double.NIN_VALUE` + * - `0.0.ulp` is `Double.MIN_VALUE` */ @SinceKotlin("1.2") @InlineOnly diff --git a/libraries/stdlib/test/numbers/MathJVMTest.kt b/libraries/stdlib/test/numbers/MathJVMTest.kt index 649ff6655ad..26c36813e5d 100644 --- a/libraries/stdlib/test/numbers/MathJVMTest.kt +++ b/libraries/stdlib/test/numbers/MathJVMTest.kt @@ -32,38 +32,4 @@ class MathJVMTest { assertEquals(PI, PI.IEEErem(Double.NEGATIVE_INFINITY)) } - @Test fun nextAndPrev() { - for (value in listOf(0.0, -0.0, Double.MIN_VALUE, -1.0, 2.0.pow(10))) { - val next = value.nextUp() - if (next > 0) { - assertEquals(next, value + value.ulp) - } else { - assertEquals(value, next - next.ulp) - } - - val prev = value.nextDown() - if (prev > 0) { - assertEquals(value, prev + prev.ulp) - } - else { - assertEquals(prev, value - value.ulp) - } - - val toZero = value.nextTowards(0.0) - if (toZero != 0.0) { - assertEquals(value, toZero + toZero.ulp.withSign(toZero)) - } - - assertEquals(Double.POSITIVE_INFINITY, Double.MAX_VALUE.nextUp()) - assertEquals(Double.MAX_VALUE, Double.POSITIVE_INFINITY.nextDown()) - - assertEquals(Double.NEGATIVE_INFINITY, (-Double.MAX_VALUE).nextDown()) - assertEquals((-Double.MAX_VALUE), Double.NEGATIVE_INFINITY.nextUp()) - - assertTrue(Double.NaN.ulp.isNaN()) - assertTrue(Double.NaN.nextDown().isNaN()) - assertTrue(Double.NaN.nextUp().isNaN()) - assertTrue(Double.NaN.nextTowards(0.0).isNaN()) - } - } } \ No newline at end of file diff --git a/libraries/stdlib/test/numbers/MathTest.kt b/libraries/stdlib/test/numbers/MathTest.kt index 8db44e364a1..ab5ba26a625 100644 --- a/libraries/stdlib/test/numbers/MathTest.kt +++ b/libraries/stdlib/test/numbers/MathTest.kt @@ -321,6 +321,50 @@ class DoubleMathTest { } } + + @Test fun nextAndPrev() { + for (value in listOf(0.0, -0.0, Double.MIN_VALUE, -1.0, 2.0.pow(10))) { + val next = value.nextUp() + if (next > 0) { + assertEquals(next, value + value.ulp) + } else { + assertEquals(value, next - next.ulp) + } + + val prev = value.nextDown() + if (prev > 0) { + assertEquals(value, prev + prev.ulp) + } + else { + assertEquals(prev, value - value.ulp) + } + + val toZero = value.nextTowards(0.0) + if (toZero != 0.0) { + assertEquals(value, toZero + toZero.ulp.withSign(toZero)) + } + + assertEquals(Double.POSITIVE_INFINITY, Double.MAX_VALUE.nextUp()) + assertEquals(Double.MAX_VALUE, Double.POSITIVE_INFINITY.nextDown()) + + assertEquals(Double.NEGATIVE_INFINITY, (-Double.MAX_VALUE).nextDown()) + assertEquals((-Double.MAX_VALUE), Double.NEGATIVE_INFINITY.nextUp()) + + assertTrue(Double.NaN.ulp.isNaN()) + assertTrue(Double.NaN.nextDown().isNaN()) + assertTrue(Double.NaN.nextUp().isNaN()) + assertTrue(Double.NaN.nextTowards(0.0).isNaN()) + + assertEquals(Double.MIN_VALUE, (0.0).ulp) + assertEquals(Double.MIN_VALUE, (-0.0).ulp) + assertEquals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY.ulp) + assertEquals(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY.ulp) + + val maxUlp = 2.0.pow(971) + assertEquals(maxUlp, Double.MAX_VALUE.ulp) + assertEquals(maxUlp, (-Double.MAX_VALUE).ulp) + } + } } class FloatMathTest {