diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java index 4d9d5da6742..0021c50fd03 100644 --- a/user/super/com/google/gwt/emul/java/lang/Math.java +++ b/user/super/com/google/gwt/emul/java/lang/Math.java @@ -17,6 +17,7 @@ import static javaemul.internal.InternalPreconditions.checkCriticalArithmetic; +import javaemul.internal.JsUtils; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsPackage; import jsinterop.annotations.JsType; @@ -25,13 +26,6 @@ * Math utility methods and constants. */ public final class Math { - // The following methods are not implemented because JS doesn't provide the - // necessary pieces: - // public static double ulp (double x) - // public static float ulp (float x) - // public static int getExponent (double d) - // public static int getExponent (float f) - // public static double IEEEremainder(double f1, double f2) public static final double E = 2.7182818284590452354; public static final double PI = 3.14159265358979323846; @@ -52,6 +46,16 @@ public static long abs(long x) { return x < 0 ? -x : x; } + public static int absExact(int v) { + checkCriticalArithmetic(v != Integer.MIN_VALUE); + return abs(v); + } + + public static long absExact(long v) { + checkCriticalArithmetic(v != Long.MIN_VALUE); + return abs(v); + } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.acos") public static native double acos(double x); @@ -77,9 +81,8 @@ public static long addExact(long x, long y) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.atan2") public static native double atan2(double y, double x); - public static double cbrt(double x) { - return x == 0 || !Double.isFinite(x) ? x : pow(x, 1.0 / 3.0); - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.cbrt") + public static native double cbrt(double x); @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.ceil") public static native double ceil(double x); @@ -99,9 +102,8 @@ public static float copySign(float magnitude, float sign) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.cos") public static native double cos(double x); - public static double cosh(double x) { - return (exp(x) + exp(-x)) / 2; - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.cosh") + public static native double cosh(double x); public static int decrementExact(int x) { checkCriticalArithmetic(x != Integer.MIN_VALUE); @@ -116,9 +118,8 @@ public static long decrementExact(long x) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.exp") public static native double exp(double x); - public static double expm1(double d) { - return d == 0 ? d : exp(d) - 1; - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.expm1") + public static native double expm1(double d); @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.floor") public static native double floor(double x); @@ -135,6 +136,10 @@ public static long floorDiv(long dividend, long divisor) { return ((dividend ^ divisor) >= 0 ? dividend / divisor : ((dividend + 1) / divisor) - 1); } + public static long floorDiv(long dividend, int divisor) { + return floorDiv(dividend, (long) divisor); + } + public static int floorMod(int dividend, int divisor) { checkCriticalArithmetic(divisor != 0); return ((dividend % divisor) + divisor) % divisor; @@ -145,11 +150,53 @@ public static long floorMod(long dividend, long divisor) { return ((dividend % divisor) + divisor) % divisor; } - public static double hypot(double x, double y) { - return Double.isInfinite(x) || Double.isInfinite(y) ? - Double.POSITIVE_INFINITY : sqrt(x * x + y * y); + public static long floorMod(long dividend, int divisor) { + return floorMod(dividend, (long) divisor); + } + + @SuppressWarnings("CheckStyle.MethodName") + public static double IEEEremainder(double v, double m) { + double ratio = v / m; + double closest = Math.ceil(ratio); + double frac = Math.abs(closest - ratio); + if (frac > 0.5 || frac == 0.5 && (closest % 2 != 0)) { + closest = Math.floor(ratio); + } + // if closest == 0 and m == inf, avoid multiplication + return closest == 0 ? v : v - m * closest; + } + + public static int getExponent(double v) { + int[] intBits = JsUtils.doubleToRawIntBits(v); + return ((intBits[1] >> 20) & 2047) - Double.MAX_EXPONENT; + } + + public static int getExponent(float v) { + return ((JsUtils.floatToRawIntBits(v) >> 23) & 255) - Float.MAX_EXPONENT; + } + + public static double ulp(double v) { + if (!Double.isFinite(v)) { + return Math.abs(v); + } + int exponent = Math.getExponent(v); + if (exponent == -1023) { + return Double.MIN_VALUE; + } + return Math.pow(2, exponent - 52); + } + + public static float ulp(float v) { + int exponent = Math.getExponent(v); + if (exponent == -Float.MAX_EXPONENT) { + return Float.MIN_VALUE; + } + return (float) Math.pow(2, exponent - 23); } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.hypot") + public static native double hypot(double x, double y); + public static int incrementExact(int x) { checkCriticalArithmetic(x != Integer.MAX_VALUE); return x + 1; @@ -163,13 +210,11 @@ public static long incrementExact(long x) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log") public static native double log(double x); - public static double log10(double x) { - return log(x) * NativeMath.LOG10E; - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log10") + public static native double log10(double x); - public static double log1p(double x) { - return x == 0 ? x : log(x + 1); - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log1p") + public static native double log1p(double x); @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.max") public static native double max(double x, double y); @@ -197,12 +242,28 @@ public static long min(long x, long y) { return x < y ? x : y; } + public static long multiplyFull(int x, int y) { + return (long) x * (long) y; + } + public static int multiplyExact(int x, int y) { double r = (double) x * (double) y; checkCriticalArithmetic(isSafeIntegerRange(r)); return (int) r; } + public static long multiplyExact(long x, int y) { + if (y == -1) { + return negateExact(x); + } + if (y == 0) { + return 0; + } + long r = x * y; + checkCriticalArithmetic(r / y == x); + return r; + } + public static long multiplyExact(long x, long y) { if (y == -1) { return negateExact(x); @@ -284,13 +345,8 @@ public static float scalb(float f, int scaleFactor) { return (float) scalb((double) f, scaleFactor); } - public static double signum(double d) { - if (d == 0 || Double.isNaN(d)) { - return d; - } else { - return d < 0 ? -1 : 1; - } - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sign") + public static native double signum(double d); public static float signum(float f) { return (float) signum((double) f); @@ -299,9 +355,8 @@ public static float signum(float f) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sin") public static native double sin(double x); - public static double sinh(double x) { - return x == 0.0 ? x : (exp(x) - exp(-x)) / 2; - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sinh") + public static native double sinh(double x); @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.sqrt") public static native double sqrt(double x); @@ -309,18 +364,8 @@ public static double sinh(double x) { @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.tan") public static native double tan(double x); - public static double tanh(double x) { - if (x == 0.0) { - // -0.0 should return -0.0. - return x; - } - - double e2x = exp(2 * x); - if (Double.isInfinite(e2x)) { - return 1; - } - return (e2x - 1) / (e2x + 1); - } + @JsMethod(namespace = JsPackage.GLOBAL, name = "Math.tanh") + public static native double tanh(double x); public static double toDegrees(double x) { return x * PI_UNDER_180; @@ -411,7 +456,6 @@ private static boolean isSafeIntegerRange(double value) { @JsType(isNative = true, name = "Math", namespace = JsPackage.GLOBAL) private static class NativeMath { - public static double LOG10E; public static native double round(double x); } } diff --git a/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java b/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java index 3fb5826c726..ba6cc5ff618 100644 --- a/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java +++ b/user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java @@ -123,10 +123,14 @@ public static float intBitsToFloat(int value) { return JsUtils.uncheckedCast(new Float32Array(buf))[0]; } - public static long doubleToRawLongBits(double value) { + public static int[] doubleToRawIntBits(double value) { ArrayBuffer buf = new ArrayBuffer(8); JsUtils.uncheckedCast(new Float64Array(buf))[0] = value; - int[] intBits = JsUtils.uncheckedCast(new Uint32Array(buf)); + return JsUtils.uncheckedCast(new Uint32Array(buf)); + } + + public static long doubleToRawLongBits(double value) { + int[] intBits = doubleToRawIntBits(value); return LongUtils.fromBits(intBits[0] | 0, intBits[1] | 0); } diff --git a/user/test-super/com/google/gwt/emultest/super/com/google/gwt/emultest/java17/lang/MathTest.java b/user/test-super/com/google/gwt/emultest/super/com/google/gwt/emultest/java17/lang/MathTest.java new file mode 100644 index 00000000000..6874d4f789b --- /dev/null +++ b/user/test-super/com/google/gwt/emultest/super/com/google/gwt/emultest/java17/lang/MathTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 2025 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.emultest.java17.lang; + +import com.google.gwt.emultest.java.util.EmulTestBase; + +/** + * Tests for java.lang.String Java 12 API emulation. + */ +public class MathTest extends EmulTestBase { + + public void testAbsExact() { + assertEquals(Integer.MAX_VALUE, Math.absExact(hideFromCompiler(Integer.MAX_VALUE))); + assertEquals(0, Math.absExact(hideFromCompiler(0))); + assertEquals(1, Math.absExact(hideFromCompiler(-1))); + assertThrowsArithmetic(() -> Math.absExact(hideFromCompiler(Integer.MIN_VALUE))); + assertEquals(Long.MAX_VALUE, Math.absExact(hideFromCompiler(Long.MAX_VALUE))); + assertEquals(0, Math.absExact(hideFromCompiler(0l))); + assertEquals(1, Math.absExact(hideFromCompiler(-1l))); + assertThrowsArithmetic(() -> Math.absExact(hideFromCompiler(Long.MIN_VALUE))); + } + + public void testAbsExactWithFolding() { + assertEquals(Integer.MAX_VALUE, Math.absExact(Integer.MAX_VALUE)); + assertEquals(0, Math.absExact(0)); + assertEquals(1, Math.absExact(-1)); + assertThrowsArithmetic(() -> Math.absExact(Integer.MIN_VALUE)); + assertEquals(Long.MAX_VALUE, Math.absExact(Long.MAX_VALUE)); + assertEquals(0, Math.absExact(0L)); + assertEquals(1, Math.absExact(-1L)); + assertThrowsArithmetic(() -> Math.absExact(Long.MIN_VALUE)); + } + + private void assertThrowsArithmetic(Runnable check) { + try { + check.run(); + fail("Should have failed"); + } catch (ArithmeticException ex) { + // good + } + } +} diff --git a/user/test/com/google/gwt/emultest/EmulJava17Suite.java b/user/test/com/google/gwt/emultest/EmulJava17Suite.java index b71f8d0f5f8..ae35859eed0 100644 --- a/user/test/com/google/gwt/emultest/EmulJava17Suite.java +++ b/user/test/com/google/gwt/emultest/EmulJava17Suite.java @@ -16,6 +16,7 @@ package com.google.gwt.emultest; import com.google.gwt.emultest.java17.lang.CharSequenceTest; +import com.google.gwt.emultest.java17.lang.MathTest; import com.google.gwt.emultest.java17.lang.StringTest; import com.google.gwt.emultest.java17.util.MapEntryTest; import com.google.gwt.emultest.java17.util.stream.CollectorsTest; @@ -32,6 +33,7 @@ @Suite.SuiteClasses({ CharSequenceTest.class, StringTest.class, + MathTest.class, CollectorsTest.class, StreamTest.class, DoubleStreamTest.class, diff --git a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java index befde65e560..191ed6cec9f 100644 --- a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java @@ -161,10 +161,8 @@ public void testDoubleConstants() { assertTrue(Double.MIN_VALUE < Double.MAX_VALUE); assertFalse(Double.NaN == Double.NaN); assertEquals(64, Double.SIZE); - // jdk1.6 assertEquals(Math.getExponent(Double.MAX_VALUE), - // Double.MAX_EXPONENT); - // jdk1.6 assertEquals(Math.getExponent(Double.MIN_NORMAL), - // Double.MIN_EXPONENT); + assertEquals(Math.getExponent(Double.MAX_VALUE), Double.MAX_EXPONENT); + assertEquals(Math.getExponent(Double.MIN_NORMAL), Double.MIN_EXPONENT); // issue 8073 - used to fail in prod mode assertFalse(Double.isInfinite(Double.NaN)); } diff --git a/user/test/com/google/gwt/emultest/java17/lang/MathTest.java b/user/test/com/google/gwt/emultest/java17/lang/MathTest.java new file mode 100644 index 00000000000..f207602ff46 --- /dev/null +++ b/user/test/com/google/gwt/emultest/java17/lang/MathTest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.emultest.java17.lang; + +import com.google.gwt.dev.util.arg.SourceLevel; +import com.google.gwt.emultest.java.util.EmulTestBase; +import com.google.gwt.junit.JUnitShell; + +public class MathTest extends EmulTestBase { + + public void testAbsExact() { + assertFalse(isGwtSourceLevel17()); + } + + public void testAbsExactWithFolding() { + assertFalse(isGwtSourceLevel17()); + } + + private boolean isGwtSourceLevel17() { + return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA17) >= 0; + } +} diff --git a/user/test/com/google/gwt/emultest/java8/lang/MathTest.java b/user/test/com/google/gwt/emultest/java8/lang/MathTest.java index 5b6f914dfed..95269a52614 100644 --- a/user/test/com/google/gwt/emultest/java8/lang/MathTest.java +++ b/user/test/com/google/gwt/emultest/java8/lang/MathTest.java @@ -28,6 +28,7 @@ public class MathTest extends GWTTestCase { private static final Integer[] ALL_INTEGER_CANDIDATES = getAllIntegerCandidates(); private static final Long[] ALL_LONG_CANDIDATES = getAllLongCandidates(); + private static final double EPS = 1E-15; @Override public String getModuleName() { @@ -104,11 +105,7 @@ public void testFloorDiv() { // special case assertEquals(Integer.MIN_VALUE, Math.floorDiv(Integer.MIN_VALUE, -1)); - try { - Math.floorDiv(1, 0); - fail(); - } catch (ArithmeticException expected) { - } + assertThrowsArithmetic(() -> Math.floorDiv(1, 0)); } public void testFloorDivLongs() { @@ -125,11 +122,17 @@ public void testFloorDivLongs() { // special case assertEquals(Long.MIN_VALUE, Math.floorDiv(Long.MIN_VALUE, -1)); - try { - Math.floorDiv(1L, 0L); - fail(); - } catch (ArithmeticException expected) { - } + assertThrowsArithmetic(() -> Math.floorDiv(1L, 0L)); + } + + public void testFloorDivLongInt() { + assertEquals(0L, Math.floorDiv(0L, 1)); + assertEquals(1L, Math.floorDiv(4L, 3)); + assertEquals((long) Integer.MAX_VALUE * 2 + 2, + Math.floorDiv(Long.MIN_VALUE, Integer.MIN_VALUE)); + assertEquals((long) Integer.MAX_VALUE * 2 + 4, + Math.floorDiv(Long.MAX_VALUE, Integer.MAX_VALUE)); + assertThrowsArithmetic(() -> Math.floorDiv(1L, 0)); } public void testFloorMod() { @@ -142,12 +145,7 @@ public void testFloorMod() { assertEquals(0, Math.floorMod(Integer.MAX_VALUE, Integer.MAX_VALUE)); assertEquals(0, Math.floorMod(Integer.MIN_VALUE, 1)); assertEquals(0, Math.floorMod(Integer.MAX_VALUE, 1)); - - try { - Math.floorMod(1, 0); - fail(); - } catch (ArithmeticException expected) { - } + assertThrowsArithmetic(() -> Math.floorMod(1, 0)); } public void testFloorModLongs() { @@ -160,12 +158,15 @@ public void testFloorModLongs() { assertEquals(0L, Math.floorMod(Long.MAX_VALUE, Long.MAX_VALUE)); assertEquals(0L, Math.floorMod(Long.MIN_VALUE, 1L)); assertEquals(0L, Math.floorMod(Long.MAX_VALUE, 1L)); + assertThrowsArithmetic(() -> Math.floorMod(1L, 0L)); + } - try { - Math.floorMod(1L, 0L); - fail(); - } catch (ArithmeticException expected) { - } + public void testFloorModLongInt() { + assertEquals(0L, Math.floorMod(0L, 1L)); + assertEquals(1L, Math.floorMod(4L, 3L)); + assertEquals(0L, Math.floorMod(Long.MIN_VALUE, Integer.MIN_VALUE)); + assertEquals(1L, Math.floorMod(Long.MAX_VALUE, Integer.MAX_VALUE)); + assertThrowsArithmetic(() -> Math.floorMod(1L, 0)); } public void testIncrementExact() { @@ -224,6 +225,45 @@ public void testMultiplyExactLongs() { } } + public void testMultiplyExactLongInt() { + for (long a : ALL_LONG_CANDIDATES) { + for (int b : ALL_INTEGER_CANDIDATES) { + BigInteger expectedResult = BigInteger.valueOf(a).multiply(BigInteger.valueOf(b)); + boolean expectedSuccess = fitsInLong(expectedResult); + try { + assertEquals(a * b, Math.multiplyExact(a, b)); + assertTrue(expectedSuccess); + } catch (ArithmeticException e) { + assertFalse(expectedSuccess); + } + } + } + } + + public void testMultiplyFull() { + assertEquals(1L, Long.MAX_VALUE + - Math.multiplyFull(Integer.MAX_VALUE, Integer.MAX_VALUE) * 2 - 4L * Integer.MAX_VALUE); + assertEquals(-1L, Long.MAX_VALUE + - 2 * Math.multiplyFull(Integer.MIN_VALUE, Integer.MIN_VALUE)); + + assertEquals(1L, hideFromCompiler(Long.MAX_VALUE) + - Math.multiplyFull(Integer.MAX_VALUE, Integer.MAX_VALUE) * 2 - 4L * Integer.MAX_VALUE); + assertEquals(-1L, hideFromCompiler(Long.MAX_VALUE) + - 2 * Math.multiplyFull(Integer.MIN_VALUE, Integer.MIN_VALUE)); + } + + public void testCbrt() { + assertEquals(-2, Math.cbrt(-8), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.cbrt(Double.POSITIVE_INFINITY), EPS); + assertEquals(Double.NEGATIVE_INFINITY, Math.cbrt(Double.NEGATIVE_INFINITY), EPS); + + assertEquals(-2, Math.cbrt(hideFromCompiler(-8)), EPS); + assertEquals(Double.POSITIVE_INFINITY, + Math.cbrt(hideFromCompiler(Double.POSITIVE_INFINITY)), EPS); + assertEquals(Double.NEGATIVE_INFINITY, + Math.cbrt(hideFromCompiler(Double.NEGATIVE_INFINITY)), EPS); + } + public void testNegateExact() { for (int a : ALL_INTEGER_CANDIDATES) { BigInteger expectedResult = BigInteger.valueOf(a).negate(); @@ -294,6 +334,244 @@ public void testToIntExact() { } } + public void testLog1p() { + assertEquals(Math.log(2), Math.log1p(1), EPS); + assertEquals(1, Math.log1p(1E-30) * 1E30, EPS); + assertEquals(-1, Math.log1p(-1E-30) * 1E30, EPS); + + assertEquals(Math.log(2), Math.log1p(hideFromCompiler(1)), EPS); + assertEquals(1, Math.log1p(hideFromCompiler(1E-30)) * 1E30, EPS); + assertEquals(-1, Math.log1p(hideFromCompiler(-1E-30)) * 1E30, EPS); + } + + public void testExpM1() { + assertEquals(Math.E - 1, Math.expm1(1), EPS); + assertEquals(1, Math.expm1(1E-30) * 1E30, EPS); + assertEquals(-1, Math.expm1(-1E-30) * 1E30, EPS); + + assertEquals(Math.E - 1, Math.expm1(hideFromCompiler(1)), EPS); + assertEquals(1, Math.expm1(hideFromCompiler(1E-30)) * 1E30, EPS); + assertEquals(-1, Math.expm1(hideFromCompiler(-1E-30)) * 1E30, EPS); + } + + public void testIEEEremainderWithFolding() { + assertEquals(1.0, Math.IEEEremainder(7.0, 3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(8.0, 3.0), EPS); + assertEquals(0.0, Math.IEEEremainder(6.0, 3.0), EPS); + assertEquals(0.0, Math.IEEEremainder(9.0, 3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(-7.0, 3.0), EPS); + assertEquals(1.0, Math.IEEEremainder(7.0, -3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(-7.0, -3.0), EPS); + assertEquals(0.5, Math.IEEEremainder(2.5, 1.0), EPS); + assertEquals(0.5, Math.IEEEremainder(2.5, 2.0), EPS); + assertEquals(0.2, Math.IEEEremainder(5.2, 1.0), EPS); + + assertEquals(0.0, Math.IEEEremainder(4.5, 1.5), EPS); + assertEquals(1.5, Math.IEEEremainder(7.5, 3.0), EPS); + assertEquals(-1.5, Math.IEEEremainder(-7.5, -3.0), EPS); + assertEquals(1.5, Math.IEEEremainder(7.5, -3.0), EPS); + assertEquals(-1.5, Math.IEEEremainder(-7.5, 3.0), EPS); + // Remainder with 0 divisor is NaN + assertTrue(Double.isNaN(Math.IEEEremainder(5.0, 0.0))); + // 0 divided by anything is 0 + assertEquals(0.0, Math.IEEEremainder(0.0, 2.0), EPS); + // Infinity cases produce NaN + assertTrue(Double.isNaN(Math.IEEEremainder(Double.POSITIVE_INFINITY, 2.0))); + assertTrue(Double.isNaN(Math.IEEEremainder(Double.NEGATIVE_INFINITY, 2.0))); + assertEquals(2, Math.IEEEremainder(2, Double.POSITIVE_INFINITY), EPS); + assertEquals(2, Math.IEEEremainder(2, Double.NEGATIVE_INFINITY), EPS); + // Any finite number divided by infinity -> same number + assertEquals(5.0, Math.IEEEremainder(5.0, Double.POSITIVE_INFINITY), EPS); + assertTrue(Double.isNaN(Math.IEEEremainder(Double.NaN, 2.0))); + assertTrue(Double.isNaN(Math.IEEEremainder(5.0, Double.NaN))); + assertTrue(Double.isNaN(Math.IEEEremainder(Double.NaN, Double.NaN))); + } + + public void testIEEEremainder() { + assertEquals(1.0, Math.IEEEremainder(hideFromCompiler(7.0), 3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(8.0), 3.0), EPS); + assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(6.0), 3.0), EPS); + assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(9.0), 3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(-7.0), 3.0), EPS); + assertEquals(1.0, Math.IEEEremainder(hideFromCompiler(7.0), -3.0), EPS); + assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(-7.0), -3.0), EPS); + assertEquals(0.5, Math.IEEEremainder(hideFromCompiler(2.5), 1.0), EPS); + assertEquals(0.5, Math.IEEEremainder(hideFromCompiler(2.5), 2.0), EPS); + assertEquals(0.2, Math.IEEEremainder(hideFromCompiler(5.2), 1.0), EPS); + + assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(4.5), 1.5), EPS); + assertEquals(1.5, Math.IEEEremainder(hideFromCompiler(7.5), 3.0), EPS); + assertEquals(-1.5, Math.IEEEremainder(hideFromCompiler(-7.5), -3.0), EPS); + assertEquals(1.5, Math.IEEEremainder(hideFromCompiler(7.5), -3.0), EPS); + assertEquals(-1.5, Math.IEEEremainder(hideFromCompiler(-7.5), 3.0), EPS); + // Remainder with 0 divisor is NaN + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(5.0), 0.0))); + // 0 divided by anything is 0 + assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(0.0), 2.0), EPS); + // Infinity cases produce NaN + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.POSITIVE_INFINITY), 2.0))); + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NEGATIVE_INFINITY), 2.0))); + assertEquals(2, Math.IEEEremainder(hideFromCompiler(2), Double.POSITIVE_INFINITY), EPS); + assertEquals(2, Math.IEEEremainder(hideFromCompiler(2), Double.NEGATIVE_INFINITY), EPS); + // Any finite number divided by infinity -> same number + assertEquals(5.0, Math.IEEEremainder(hideFromCompiler(5.0), Double.POSITIVE_INFINITY), EPS); + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NaN), 2.0))); + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(5.0), Double.NaN))); + assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NaN), Double.NaN))); + } + + public void testUlpWithFolding() { + double ulpEps = 1e-50; + assertEquals(2.220446049250313E-16, Math.ulp(1.0), ulpEps); + assertEquals(2.220446049250313E-16, Math.ulp(-1.0), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(2.0), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(-2.0), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(3.0), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(-3.0), ulpEps); + assertEquals(1.99584030953472E292, Math.ulp(Double.MAX_VALUE), EPS); + assertEquals(1.99584030953472E292, Math.ulp(-Double.MAX_VALUE), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.ulp(Double.NEGATIVE_INFINITY), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.ulp(Double.POSITIVE_INFINITY), EPS); + assertTrue(Double.isNaN(Math.ulp(Double.NaN))); + assertEquals(Double.MIN_VALUE, Math.ulp(Double.MIN_NORMAL), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(-Double.MIN_NORMAL), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(0.0), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(-0.0), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(8.289046E-317), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(-8.289046E-317), 0); + + assertEquals(1.1920929E-7f, Math.ulp(1.0f), EPS); + assertEquals(1.1920929E-7f, Math.ulp(-1.0f), EPS); + assertEquals(2.3841858E-7f, Math.ulp(2.0f), EPS); + assertEquals(2.3841858E-7f, Math.ulp(-2.0f), EPS); + assertEquals(2.3841858E-7f, Math.ulp(3.0f), EPS); + assertEquals(2.3841858E-7f, Math.ulp(-3.0f), EPS); + assertEquals(2.028241E31f, Math.ulp(Float.MAX_VALUE), EPS); + assertEquals(2.028241E31f, Math.ulp(-Float.MAX_VALUE), EPS); + assertEquals(Float.MIN_VALUE, Math.ulp(Float.MIN_VALUE), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(-Float.MIN_VALUE), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(Float.MIN_NORMAL), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(-Float.MIN_NORMAL), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(0.0f), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(-0.0f), 0); + } + + public void testUlp() { + double ulpEps = 1e-50; + assertEquals(2.220446049250313E-16, Math.ulp(hideFromCompiler(1.0)), ulpEps); + assertEquals(2.220446049250313E-16, Math.ulp(hideFromCompiler(-1.0)), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(hideFromCompiler(2.0)), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(hideFromCompiler(-2.0)), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(hideFromCompiler(3.0)), ulpEps); + assertEquals(4.440892098500626E-16, Math.ulp(hideFromCompiler(-3.0)), ulpEps); + assertEquals(1.99584030953472E292, Math.ulp(hideFromCompiler(Double.MAX_VALUE)), EPS); + assertEquals(1.99584030953472E292, Math.ulp(hideFromCompiler(-Double.MAX_VALUE)), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.ulp(hideFromCompiler(Double.NEGATIVE_INFINITY)), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.ulp(hideFromCompiler(Double.POSITIVE_INFINITY)), EPS); + assertTrue(Double.isNaN(Math.ulp(hideFromCompiler(Double.NaN)))); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(Double.MIN_NORMAL)), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(-Double.MIN_NORMAL)), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(0.0)), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(-0.0)), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(8.289046E-317)), 0); + assertEquals(Double.MIN_VALUE, Math.ulp(hideFromCompiler(-8.289046E-317)), 0); + + assertEquals(1.1920929E-7f, Math.ulp(hideFromCompiler(1.0f)), EPS); + assertEquals(1.1920929E-7f, Math.ulp(hideFromCompiler(-1.0f)), EPS); + assertEquals(2.3841858E-7f, Math.ulp(hideFromCompiler(2.0f)), EPS); + assertEquals(2.3841858E-7f, Math.ulp(hideFromCompiler(-2.0f)), EPS); + assertEquals(2.3841858E-7f, Math.ulp(hideFromCompiler(3.0f)), EPS); + assertEquals(2.3841858E-7f, Math.ulp(hideFromCompiler(-3.0f)), EPS); + assertEquals(2.028241E31f, Math.ulp(hideFromCompiler(Float.MAX_VALUE)), EPS); + assertEquals(2.028241E31f, Math.ulp(hideFromCompiler(-Float.MAX_VALUE)), EPS); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(Float.MIN_VALUE)), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(-Float.MIN_VALUE)), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(Float.MIN_NORMAL)), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(-Float.MIN_NORMAL)), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(0.0f)), 0); + assertEquals(Float.MIN_VALUE, Math.ulp(hideFromCompiler(-0.0f)), 0); + } + + public void testHypot() { + assertEquals(5.0, Math.hypot(3.0, 4.0), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(1, Double.NEGATIVE_INFINITY), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(Double.POSITIVE_INFINITY, 1), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(Double.NaN, Double.NEGATIVE_INFINITY), EPS); + + assertEquals(5.0, Math.hypot(hideFromCompiler(3.0), 4.0), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(-1), + Double.NEGATIVE_INFINITY), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(Double.POSITIVE_INFINITY), + 1), EPS); + assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(Double.NaN), + Double.NEGATIVE_INFINITY), EPS); + } + + public void testGetExponentWithFolding() { + assertEquals(1, Math.getExponent(2d)); + assertEquals(1, Math.getExponent(3d)); + assertEquals(2, Math.getExponent(4d)); + assertEquals(-1023, Math.getExponent(0d)); + assertEquals(1023, Math.getExponent(Math.pow(2, 1023))); + assertEquals(-1023, Math.getExponent(Math.pow(2, -1023))); + assertEquals(1023, Math.getExponent(-Math.pow(2, 1023))); + assertEquals(-1023, Math.getExponent(-Math.pow(2, -1023))); + assertEquals(1024, Math.getExponent(Double.POSITIVE_INFINITY)); + assertEquals(1024, Math.getExponent(Double.NEGATIVE_INFINITY)); + assertEquals(1024, Math.getExponent(Double.NaN)); + + assertEquals(2, Math.getExponent(4f)); + assertEquals(-127, Math.getExponent(0f)); + + assertEquals(126, Math.getExponent((float) Math.pow(2, 126))); + assertEquals(-126, Math.getExponent((float) Math.pow(2, -126))); + assertEquals(126, Math.getExponent((float) -Math.pow(2, 126))); + assertEquals(-126, Math.getExponent((float) -Math.pow(2, -126))); + assertEquals(128, Math.getExponent((float) Math.pow(2, 500))); + assertEquals(-127, Math.getExponent((float) Math.pow(2, -500))); + } + + public void testGetExponent() { + assertEquals(1, Math.getExponent(hideFromCompiler(2d))); + assertEquals(1, Math.getExponent(hideFromCompiler(3d))); + assertEquals(2, Math.getExponent(hideFromCompiler(4d))); + assertEquals(-1023, Math.getExponent(hideFromCompiler(0d))); + assertEquals(1023, Math.getExponent(hideFromCompiler(Math.pow(2, 1023)))); + assertEquals(-1023, Math.getExponent(hideFromCompiler(Math.pow(2, -1023)))); + assertEquals(1023, Math.getExponent(hideFromCompiler(-Math.pow(2, 1023)))); + assertEquals(-1023, Math.getExponent(hideFromCompiler(-Math.pow(2, -1023)))); + assertEquals(1024, Math.getExponent(hideFromCompiler(Double.POSITIVE_INFINITY))); + assertEquals(1024, Math.getExponent(hideFromCompiler(Double.NEGATIVE_INFINITY))); + assertEquals(1024, Math.getExponent(hideFromCompiler(Double.NaN))); + + assertEquals(2, Math.getExponent(hideFromCompiler(4f))); + assertEquals(-127, Math.getExponent(hideFromCompiler(0f))); + + assertEquals(126, Math.getExponent(hideFromCompiler((float) Math.pow(2, 126)))); + assertEquals(-126, Math.getExponent(hideFromCompiler((float) Math.pow(2, -126)))); + assertEquals(126, Math.getExponent(hideFromCompiler((float) -Math.pow(2, 126)))); + assertEquals(-126, Math.getExponent(hideFromCompiler((float) -Math.pow(2, -126)))); + assertEquals(128, Math.getExponent(hideFromCompiler((float) Math.pow(2, 500)))); + assertEquals(-127, Math.getExponent(hideFromCompiler((float) Math.pow(2, -500)))); + } + + private T hideFromCompiler(T value) { + if (Math.random() < -1) { + // Can never happen, but fools the compiler enough not to optimize this call. + fail(); + } + return value; + } + + private void assertThrowsArithmetic(Runnable check) { + try { + check.run(); + fail("Should have failed"); + } catch (ArithmeticException ex) { + // good + } + } + private static boolean fitsInInt(BigInteger big) { return big.bitLength() < Integer.SIZE; } diff --git a/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java b/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java index 6317d90fd47..42649b93fa5 100644 --- a/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java +++ b/user/test/com/google/gwt/emultest/java8/util/DoubleSummaryStatisticsTest.java @@ -48,15 +48,13 @@ public void testAverageAndSumWithCompensation() throws Exception { double initial = 1.0d; long count = 100000; - // 'precision' is the hardcoded result of Math.ulp(initial) in JVM, - // since GWT does not emulate Math.ulp(). - // This value represents the distance from 'initial' (1.0d) to the + // The value of 'precision' represents the distance from 'initial' (1.0d) to the // previous/next double. If we add half or less of that distance/precision // to 'initial' then the result will be truncated to 'initial' again due to // floating point arithmetic rounding. // With Kahan summation such rounding errors are detected and compensated // so the summation result should (nearly) equal the expected sum. - double precision = 2.220446049250313E-16; + double precision = Math.ulp(initial); double value = precision / 2; double expectedSum = initial + (count * value); long expectedCount = count + 1;