Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 92 additions & 48 deletions user/super/com/google/gwt/emul/java/lang/Math.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -299,28 +355,17 @@ 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);

@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;
Expand Down Expand Up @@ -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);
}
}
8 changes: 6 additions & 2 deletions user/super/com/google/gwt/emul/javaemul/internal/JsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,14 @@ public static float intBitsToFloat(int value) {
return JsUtils.<float[]>uncheckedCast(new Float32Array(buf))[0];
}

public static long doubleToRawLongBits(double value) {
public static int[] doubleToRawIntBits(double value) {
ArrayBuffer buf = new ArrayBuffer(8);
JsUtils.<double[]>uncheckedCast(new Float64Array(buf))[0] = value;
int[] intBits = JsUtils.<int[]>uncheckedCast(new Uint32Array(buf));
return JsUtils.<int[]>uncheckedCast(new Uint32Array(buf));
}

public static long doubleToRawLongBits(double value) {
int[] intBits = doubleToRawIntBits(value);
return LongUtils.fromBits(intBits[0] | 0, intBits[1] | 0);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
}
2 changes: 2 additions & 0 deletions user/test/com/google/gwt/emultest/EmulJava17Suite.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +33,7 @@
@Suite.SuiteClasses({
CharSequenceTest.class,
StringTest.class,
MathTest.class,
CollectorsTest.class,
StreamTest.class,
DoubleStreamTest.class,
Expand Down
6 changes: 2 additions & 4 deletions user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
35 changes: 35 additions & 0 deletions user/test/com/google/gwt/emultest/java17/lang/MathTest.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading