Skip to content

Commit e52704b

Browse files
committed
Math emulation improvements
1 parent 7b221c1 commit e52704b

File tree

6 files changed

+293
-12
lines changed

6 files changed

+293
-12
lines changed

user/super/com/google/gwt/emul/java/lang/Math.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static javaemul.internal.InternalPreconditions.checkCriticalArithmetic;
1919

20+
import javaemul.internal.JsUtils;
2021
import jsinterop.annotations.JsMethod;
2122
import jsinterop.annotations.JsPackage;
2223
import jsinterop.annotations.JsType;
@@ -31,7 +32,6 @@ public final class Math {
3132
// public static float ulp (float x)
3233
// public static int getExponent (double d)
3334
// public static int getExponent (float f)
34-
// public static double IEEEremainder(double f1, double f2)
3535

3636
public static final double E = 2.7182818284590452354;
3737
public static final double PI = 3.14159265358979323846;
@@ -52,6 +52,16 @@ public static long abs(long x) {
5252
return x < 0 ? -x : x;
5353
}
5454

55+
public static int absExact(int v) {
56+
checkCriticalArithmetic(v != Integer.MIN_VALUE);
57+
return abs(v);
58+
}
59+
60+
public static long absExact(long v) {
61+
checkCriticalArithmetic(v != Long.MIN_VALUE);
62+
return abs(v);
63+
}
64+
5565
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.acos")
5666
public static native double acos(double x);
5767

@@ -116,9 +126,8 @@ public static long decrementExact(long x) {
116126
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.exp")
117127
public static native double exp(double x);
118128

119-
public static double expm1(double d) {
120-
return d == 0 ? d : exp(d) - 1;
121-
}
129+
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.expm1")
130+
public static native double expm1(double d);
122131

123132
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.floor")
124133
public static native double floor(double x);
@@ -145,11 +154,30 @@ public static long floorMod(long dividend, long divisor) {
145154
return ((dividend % divisor) + divisor) % divisor;
146155
}
147156

148-
public static double hypot(double x, double y) {
149-
return Double.isInfinite(x) || Double.isInfinite(y) ?
150-
Double.POSITIVE_INFINITY : sqrt(x * x + y * y);
157+
@SuppressWarnings("CheckStyle.MethodName")
158+
public static double IEEEremainder(double v, double m) {
159+
double ratio = v / m;
160+
double closest = Math.ceil(ratio);
161+
double frac = Math.abs(closest - ratio);
162+
if (frac > 0.5 || frac == 0.5 && (closest % 2 != 0)) {
163+
closest = Math.floor(ratio);
164+
}
165+
// if closest == 0 and m == inf, avoid multiplication
166+
return closest == 0 ? v : v - m * closest;
167+
}
168+
169+
public static int getExponent(double v) {
170+
int[] intBits = JsUtils.doubleToRawIntBits(v);
171+
return ((intBits[1] >> 20) & 2047) - Double.MAX_EXPONENT;
172+
}
173+
174+
public static int getExponent(float v) {
175+
return ((JsUtils.floatToRawIntBits(v) >> 23) & 255) - Float.MAX_EXPONENT;
151176
}
152177

178+
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.hypot")
179+
public static native double hypot(double x, double y);
180+
153181
public static int incrementExact(int x) {
154182
checkCriticalArithmetic(x != Integer.MAX_VALUE);
155183
return x + 1;
@@ -167,9 +195,8 @@ public static double log10(double x) {
167195
return log(x) * NativeMath.LOG10E;
168196
}
169197

170-
public static double log1p(double x) {
171-
return x == 0 ? x : log(x + 1);
172-
}
198+
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.log1p")
199+
public static native double log1p(double x);
173200

174201
@JsMethod(namespace = JsPackage.GLOBAL, name = "Math.max")
175202
public static native double max(double x, double y);

user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,14 @@ public static float intBitsToFloat(int value) {
123123
return JsUtils.<float[]>uncheckedCast(new Float32Array(buf))[0];
124124
}
125125

126-
public static long doubleToRawLongBits(double value) {
126+
public static int[] doubleToRawIntBits(double value) {
127127
ArrayBuffer buf = new ArrayBuffer(8);
128128
JsUtils.<double[]>uncheckedCast(new Float64Array(buf))[0] = value;
129-
int[] intBits = JsUtils.<int[]>uncheckedCast(new Uint32Array(buf));
129+
return JsUtils.<int[]>uncheckedCast(new Uint32Array(buf));
130+
}
131+
132+
public static long doubleToRawLongBits(double value) {
133+
int[] intBits = doubleToRawIntBits(value);
130134
return LongUtils.fromBits(intBits[0] | 0, intBits[1] | 0);
131135
}
132136

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 GWT Project Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.gwt.emultest.java17.lang;
17+
18+
import com.google.gwt.emultest.java.util.EmulTestBase;
19+
20+
/**
21+
* Tests for java.lang.String Java 12 API emulation.
22+
*/
23+
public class StringTest extends EmulTestBase {
24+
25+
public void testAbsExact() {
26+
assertEquals(Integer.MAX_VALUE, Math.absExact(hideFromCompiler(Integer.MAX_VALUE)));
27+
assertEquals(0, Math.absExact(hideFromCompiler(0)));
28+
assertEquals(1, Math.absExact(hideFromCompiler(-1)));
29+
assertThrowsArithmetic(() -> Math.absExact(hideFromCompiler(Integer.MIN_VALUE)));
30+
assertEquals(Long.MAX_VALUE, Math.absExact(hideFromCompiler(Long.MAX_VALUE)));
31+
assertEquals(0, Math.absExact(hideFromCompiler(0l)));
32+
assertEquals(1, Math.absExact(hideFromCompiler(-1l)));
33+
assertThrowsArithmetic(() -> Math.absExact(hideFromCompiler(Long.MIN_VALUE)));
34+
}
35+
36+
public void testAbsExactWithFolding() {
37+
assertEquals(Integer.MAX_VALUE, Math.absExact(Integer.MAX_VALUE));
38+
assertEquals(0, Math.absExact(0));
39+
assertEquals(1, Math.absExact(-1));
40+
assertThrowsArithmetic(() -> Math.absExact(Integer.MIN_VALUE));
41+
assertEquals(Long.MAX_VALUE, Math.absExact(Long.MAX_VALUE));
42+
assertEquals(0, Math.absExact(0L));
43+
assertEquals(1, Math.absExact(-1L));
44+
assertThrowsArithmetic(() -> Math.absExact(Long.MIN_VALUE));
45+
}
46+
}

user/test/com/google/gwt/emultest/EmulJava17Suite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.gwt.emultest;
1717

1818
import com.google.gwt.emultest.java17.lang.CharSequenceTest;
19+
import com.google.gwt.emultest.java17.lang.MathTest;
1920
import com.google.gwt.emultest.java17.lang.StringTest;
2021
import com.google.gwt.emultest.java17.util.MapEntryTest;
2122
import com.google.gwt.emultest.java17.util.stream.CollectorsTest;
@@ -32,6 +33,7 @@
3233
@Suite.SuiteClasses({
3334
CharSequenceTest.class,
3435
StringTest.class,
36+
MathTest.class,
3537
CollectorsTest.class,
3638
StreamTest.class,
3739
DoubleStreamTest.class,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2025 GWT Project Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.google.gwt.emultest.java17.lang;
17+
18+
import com.google.gwt.dev.util.arg.SourceLevel;
19+
import com.google.gwt.emultest.java.util.EmulTestBase;
20+
import com.google.gwt.junit.JUnitShell;
21+
22+
public class MathTest extends EmulTestBase {
23+
24+
public void testAbsExact() {
25+
assertFalse(isGwtSourceLevel17());
26+
}
27+
28+
public void testAbsExactWithFolding() {
29+
assertFalse(isGwtSourceLevel17());
30+
}
31+
32+
private boolean isGwtSourceLevel17() {
33+
return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA17) >= 0;
34+
}
35+
}

user/test/com/google/gwt/emultest/java8/lang/MathTest.java

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class MathTest extends GWTTestCase {
2828

2929
private static final Integer[] ALL_INTEGER_CANDIDATES = getAllIntegerCandidates();
3030
private static final Long[] ALL_LONG_CANDIDATES = getAllLongCandidates();
31+
private static final double EPS = 1E-15;
3132

3233
@Override
3334
public String getModuleName() {
@@ -294,6 +295,172 @@ public void testToIntExact() {
294295
}
295296
}
296297

298+
public void testLog1p() {
299+
assertEquals(Math.log(2), Math.log1p(1), EPS);
300+
assertEquals(1, Math.log1p(1E-30) * 1E30, EPS);
301+
assertEquals(-1, Math.log1p(-1E-30) * 1E30, EPS);
302+
303+
assertEquals(Math.log(2), Math.log1p(hideFromCompiler(1)), EPS);
304+
assertEquals(1, Math.log1p(hideFromCompiler(1E-30)) * 1E30, EPS);
305+
assertEquals(-1, Math.log1p(hideFromCompiler(-1E-30)) * 1E30, EPS);
306+
}
307+
308+
public void testExpM1() {
309+
assertEquals(Math.E - 1, Math.expm1(1), EPS);
310+
assertEquals(1, Math.expm1(1E-30) * 1E30, EPS);
311+
assertEquals(-1, Math.expm1(-1E-30) * 1E30, EPS);
312+
313+
assertEquals(Math.E - 1, Math.expm1(hideFromCompiler(1)), EPS);
314+
assertEquals(1, Math.expm1(hideFromCompiler(1E-30)) * 1E30, EPS);
315+
assertEquals(-1, Math.expm1(hideFromCompiler(-1E-30)) * 1E30, EPS);
316+
}
317+
318+
public void testIEEEremainderWithFolding() {
319+
assertEquals(1.0, Math.IEEEremainder(7.0, 3.0), EPS);
320+
assertEquals(-1.0, Math.IEEEremainder(8.0, 3.0), EPS);
321+
assertEquals(0.0, Math.IEEEremainder(6.0, 3.0), EPS);
322+
assertEquals(0.0, Math.IEEEremainder(9.0, 3.0), EPS);
323+
assertEquals(-1.0, Math.IEEEremainder(-7.0, 3.0), EPS);
324+
assertEquals(1.0, Math.IEEEremainder(7.0, -3.0), EPS);
325+
assertEquals(-1.0, Math.IEEEremainder(-7.0, -3.0), EPS);
326+
assertEquals(0.5, Math.IEEEremainder(2.5, 1.0), EPS);
327+
assertEquals(0.5, Math.IEEEremainder(2.5, 2.0), EPS);
328+
assertEquals(0.2, Math.IEEEremainder(5.2, 1.0), EPS);
329+
330+
assertEquals(0.0, Math.IEEEremainder(4.5, 1.5), EPS);
331+
assertEquals(1.5, Math.IEEEremainder(7.5, 3.0), EPS);
332+
assertEquals(-1.5, Math.IEEEremainder(-7.5, -3.0), EPS);
333+
assertEquals(1.5, Math.IEEEremainder(7.5, -3.0), EPS);
334+
assertEquals(-1.5, Math.IEEEremainder(-7.5, 3.0), EPS);
335+
// Remainder with 0 divisor is NaN
336+
assertTrue(Double.isNaN(Math.IEEEremainder(5.0, 0.0)));
337+
// 0 divided by anything is 0
338+
assertEquals(0.0, Math.IEEEremainder(0.0, 2.0), EPS);
339+
// Infinity cases produce NaN
340+
assertTrue(Double.isNaN(Math.IEEEremainder(Double.POSITIVE_INFINITY, 2.0)));
341+
assertTrue(Double.isNaN(Math.IEEEremainder(Double.NEGATIVE_INFINITY, 2.0)));
342+
assertEquals(2, Math.IEEEremainder(2, Double.POSITIVE_INFINITY), EPS);
343+
assertEquals(2, Math.IEEEremainder(2, Double.NEGATIVE_INFINITY), EPS);
344+
// Any finite number divided by infinity -> same number
345+
assertEquals(5.0, Math.IEEEremainder(5.0, Double.POSITIVE_INFINITY), EPS);
346+
assertTrue(Double.isNaN(Math.IEEEremainder(Double.NaN, 2.0)));
347+
assertTrue(Double.isNaN(Math.IEEEremainder(5.0, Double.NaN)));
348+
assertTrue(Double.isNaN(Math.IEEEremainder(Double.NaN, Double.NaN)));
349+
}
350+
351+
public void testIEEEremainder() {
352+
assertEquals(1.0, Math.IEEEremainder(hideFromCompiler(7.0), 3.0), EPS);
353+
assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(8.0), 3.0), EPS);
354+
assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(6.0), 3.0), EPS);
355+
assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(9.0), 3.0), EPS);
356+
assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(-7.0), 3.0), EPS);
357+
assertEquals(1.0, Math.IEEEremainder(hideFromCompiler(7.0), -3.0), EPS);
358+
assertEquals(-1.0, Math.IEEEremainder(hideFromCompiler(-7.0), -3.0), EPS);
359+
assertEquals(0.5, Math.IEEEremainder(hideFromCompiler(2.5), 1.0), EPS);
360+
assertEquals(0.5, Math.IEEEremainder(hideFromCompiler(2.5), 2.0), EPS);
361+
assertEquals(0.2, Math.IEEEremainder(hideFromCompiler(5.2), 1.0), EPS);
362+
363+
assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(4.5), 1.5), EPS);
364+
assertEquals(1.5, Math.IEEEremainder(hideFromCompiler(7.5), 3.0), EPS);
365+
assertEquals(-1.5, Math.IEEEremainder(hideFromCompiler(-7.5), -3.0), EPS);
366+
assertEquals(1.5, Math.IEEEremainder(hideFromCompiler(7.5), -3.0), EPS);
367+
assertEquals(-1.5, Math.IEEEremainder(hideFromCompiler(-7.5), 3.0), EPS);
368+
// Remainder with 0 divisor is NaN
369+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(5.0), 0.0)));
370+
// 0 divided by anything is 0
371+
assertEquals(0.0, Math.IEEEremainder(hideFromCompiler(0.0), 2.0), EPS);
372+
// Infinity cases produce NaN
373+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.POSITIVE_INFINITY), 2.0)));
374+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NEGATIVE_INFINITY), 2.0)));
375+
assertEquals(2, Math.IEEEremainder(hideFromCompiler(2), Double.POSITIVE_INFINITY), EPS);
376+
assertEquals(2, Math.IEEEremainder(hideFromCompiler(2), Double.NEGATIVE_INFINITY), EPS);
377+
// Any finite number divided by infinity -> same number
378+
assertEquals(5.0, Math.IEEEremainder(hideFromCompiler(5.0), Double.POSITIVE_INFINITY), EPS);
379+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NaN), 2.0)));
380+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(5.0), Double.NaN)));
381+
assertTrue(Double.isNaN(Math.IEEEremainder(hideFromCompiler(Double.NaN), Double.NaN)));
382+
}
383+
384+
public void testHypot() {
385+
assertEquals(5.0, Math.hypot(3.0, 4.0), EPS);
386+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(1, Double.NEGATIVE_INFINITY));
387+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(Double.POSITIVE_INFINITY, 1));
388+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(Double.NaN, Double.NEGATIVE_INFINITY));
389+
390+
assertEquals(5.0, Math.hypot(hideFromCompiler(3.0), 4.0), EPS);
391+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(-1),
392+
Double.NEGATIVE_INFINITY));
393+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(Double.POSITIVE_INFINITY),
394+
1));
395+
assertEquals(Double.POSITIVE_INFINITY, Math.hypot(hideFromCompiler(Double.NaN),
396+
Double.NEGATIVE_INFINITY));
397+
}
398+
399+
public void testGetExponentWithFolding() {
400+
assertEquals(1, Math.getExponent(2d));
401+
assertEquals(1, Math.getExponent(3d));
402+
assertEquals(2, Math.getExponent(4d));
403+
assertEquals(-1023, Math.getExponent(0d));
404+
assertEquals(1023, Math.getExponent(Math.pow(2, 1023)));
405+
assertEquals(-1023, Math.getExponent(Math.pow(2, -1023)));
406+
assertEquals(1023, Math.getExponent(-Math.pow(2, 1023)));
407+
assertEquals(-1023, Math.getExponent(-Math.pow(2, -1023)));
408+
assertEquals(1024, Math.getExponent(Double.POSITIVE_INFINITY));
409+
assertEquals(1024, Math.getExponent(Double.NEGATIVE_INFINITY));
410+
assertEquals(1024, Math.getExponent(Double.NaN));
411+
412+
assertEquals(2, Math.getExponent(4f));
413+
assertEquals(-127, Math.getExponent(0f));
414+
415+
assertEquals(126, Math.getExponent((float) Math.pow(2, 126)));
416+
assertEquals(-126, Math.getExponent((float) Math.pow(2, -126)));
417+
assertEquals(126, Math.getExponent((float) -Math.pow(2, 126)));
418+
assertEquals(-126, Math.getExponent((float) -Math.pow(2, -126)));
419+
assertEquals(128, Math.getExponent((float) Math.pow(2, 500)));
420+
assertEquals(-127, Math.getExponent((float) Math.pow(2, -500)));
421+
}
422+
423+
public void testGetExponent() {
424+
assertEquals(1, Math.getExponent(hideFromCompiler(2d)));
425+
assertEquals(1, Math.getExponent(hideFromCompiler(3d)));
426+
assertEquals(2, Math.getExponent(hideFromCompiler(4d)));
427+
assertEquals(-1023, Math.getExponent(hideFromCompiler(0d)));
428+
assertEquals(1023, Math.getExponent(hideFromCompiler(Math.pow(2, 1023))));
429+
assertEquals(-1023, Math.getExponent(hideFromCompiler(Math.pow(2, -1023))));
430+
assertEquals(1023, Math.getExponent(hideFromCompiler(-Math.pow(2, 1023))));
431+
assertEquals(-1023, Math.getExponent(hideFromCompiler(-Math.pow(2, -1023))));
432+
assertEquals(1024, Math.getExponent(hideFromCompiler(Double.POSITIVE_INFINITY)));
433+
assertEquals(1024, Math.getExponent(hideFromCompiler(Double.NEGATIVE_INFINITY)));
434+
assertEquals(1024, Math.getExponent(hideFromCompiler(Double.NaN)));
435+
436+
assertEquals(2, Math.getExponent(hideFromCompiler(4f)));
437+
assertEquals(-127, Math.getExponent(hideFromCompiler(0f)));
438+
439+
assertEquals(126, Math.getExponent(hideFromCompiler((float) Math.pow(2, 126))));
440+
assertEquals(-126, Math.getExponent(hideFromCompiler((float) Math.pow(2, -126))));
441+
assertEquals(126, Math.getExponent(hideFromCompiler((float) -Math.pow(2, 126))));
442+
assertEquals(-126, Math.getExponent(hideFromCompiler((float) -Math.pow(2, -126))));
443+
assertEquals(128, Math.getExponent(hideFromCompiler((float) Math.pow(2, 500))));
444+
assertEquals(-127, Math.getExponent(hideFromCompiler((float) Math.pow(2, -500))));
445+
}
446+
447+
private <T> T hideFromCompiler(T value) {
448+
if (Math.random() < -1) {
449+
// Can never happen, but fools the compiler enough not to optimize this call.
450+
fail();
451+
}
452+
return value;
453+
}
454+
455+
private void assertThrowsArithmetic(Runnable check) {
456+
try {
457+
check.run();
458+
fail("Should have failed");
459+
} catch (ArithmeticException ex) {
460+
// good
461+
}
462+
}
463+
297464
private static boolean fitsInInt(BigInteger big) {
298465
return big.bitLength() < Integer.SIZE;
299466
}

0 commit comments

Comments
 (0)