diff --git a/javalib/src/main/scala/java/lang/Math.scala b/javalib/src/main/scala/java/lang/Math.scala index 7fe386b7a7..99ee895f05 100644 --- a/javalib/src/main/scala/java/lang/Math.scala +++ b/javalib/src/main/scala/java/lang/Math.scala @@ -53,71 +53,38 @@ object Math { // Wasm intrinsic def rint(a: scala.Double): scala.Double = { - /* Is the integer-valued `x` odd? Fused by hand of `(x.toLong & 1L) != 0L`. - * Corner cases: returns false for Infinities and NaN. - */ - @inline def isOdd(x: scala.Double): scala.Boolean = - (x.asInstanceOf[js.Dynamic] & 1.asInstanceOf[js.Dynamic]).asInstanceOf[Int] != 0 - - /* js.Math.round(a) does *almost* what we want. It rounds to nearest, - * breaking ties *up*. We need to break ties to *even*. So we need to - * detect ties, and for them, detect if we rounded to odd instead of even. - * - * The reasons why the apparently simple algorithm below works are subtle, - * and vary a lot depending on the range of `a`: - * - * - a is NaN - * r is NaN, then the == is false - * -> return r - * - * - a is +-Infinity - * r == a, then == is true! but isOdd(r) is false - * -> return r - * - * - 2**53 <= abs(a) < Infinity - * r == a, r - 0.5 rounds back to a so == is true! - * fortunately, isOdd(r) is false because all a >= 2**53 are even - * -> return r + /* We apply the technique described in Section II of + * Claude-Pierre Jeannerod, Jean-Michel Muller, Paul Zimmermann. + * On various ways to split a floating-point number. + * ARITH 2018 - 25th IEEE Symposium on Computer Arithmetic, + * Jun 2018, Amherst (MA), United States. + * pp.53-60, 10.1109/ARITH.2018.8464793. hal-01774587v2 + * available at + * https://hal.inria.fr/hal-01774587v2/document + * with β = 2, p = 53, and C = 2^(p-1) = 2^52. * - * - 2**52 <= abs(a) < 2**53 - * r == a (because all a's are integers in that range) - * - a is odd - * r - 0.5 rounds down (towards even) to r - 1.0 - * so a == r - 0.5 is false - * -> return r - * - a is even - * r - 0.5 rounds back up! (towards even) to r - * so a == r - 0.5 is true! - * but, isOdd(r) is false - * -> return r + * That is only valid for values x <= 2^52. Fortunately, all values that + * are >= 2^52 are already integers, so we can return them as is. * - * - 0.5 < abs(a) < 2**52 - * then -2**52 + 0.5 <= a <= 2**52 - 0.5 (because values in-between are not representable) - * since Math.round rounds *up* on ties, r is an integer in the range (-2**52, 2**52] - * r - 0.5 is therefore lossless - * so a == r - 0.5 accurately detects ties, and isOdd(r) breaks ties - * -> return `r`` or `r - 1.0` - * - * - a == +0.5 - * r == 1.0 - * a == r - 0.5 is true and isOdd(r) is true - * -> return `r - 1.0`, which is +0.0 - * - * - a == -0.5 - * r == -0.0 - * a == r - 0.5 is true and isOdd(r) is false - * -> return `r`, which is -0.0 - * - * - 0.0 <= abs(a) < 0.5 - * r == 0.0 with the same sign as a - * a == r - 0.5 is false - * -> return r + * We cannot use "the 1.5 trick" with C = 2^(p-1) + 2^(p-2) to handle + * negative numbers, because that would further reduce the range of valid + * `x` to maximum 2^51, although we actually need it up to 2^52. Therefore, + * we have a separate branch for negative numbers. This also allows to + * gracefully deal with the fact that we need to return -0.0 for values in + * the range [-0.5,-0.0). */ - val r = js.Math.round(a) - if ((a == r - 0.5) && isOdd(r)) - r - 1.0 - else - r + val C = 4503599627370496.0 // 2^52 + if (a > 0) { + if (a >= C) a + else (C + a) - C + } else if (a < 0) { + // do not "optimize" as `C - (C - a)`, as it would return +0.0 where it should return -0.0 + if (a <= -C) a + else -((C - a) - C) + } else { + // Handling zeroes here avoids the need to distinguish +0.0 from -0.0 + a // 0.0, -0.0 and NaN + } } @inline def round(a: scala.Float): scala.Int = js.Math.round(a).toInt