diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java index 30e1b2eca..b97ad798b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java @@ -164,6 +164,137 @@ public final class TMath extends TObject { return a - floorDiv(a, b) * b; } + public static int incrementExact(int a) { + if (a == Integer.MAX_VALUE) { + throw new ArithmeticException(); + } + return a + 1; + } + + public static long incrementExact(long a) { + if (a == Long.MAX_VALUE) { + throw new ArithmeticException(); + } + return a + 1L; + } + + public static int decrementExact(int a) { + if (a == Integer.MIN_VALUE) { + throw new ArithmeticException(); + } + return a - 1; + } + + public static long decrementExact(long a) { + if (a == Long.MIN_VALUE) { + throw new ArithmeticException(); + } + return a - 1L; + } + + public static int negateExact(int a) { + if (a == Integer.MIN_VALUE) { + throw new ArithmeticException(); + } + return -a; + } + + public static long negateExact(long a) { + if (a == Long.MIN_VALUE) { + throw new ArithmeticException(); + } + return -a; + } + + public static int toIntExact(long value) { + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new ArithmeticException(); + } + return (int) value; + } + + public static int addExact(int a, int b) { + int sum = a + b; + if ((a ^ sum) < 0 && (a ^ b) >= 0) { // a and b samesigned, but sum is not + throw new ArithmeticException(); + } + return sum; + } + + public static long addExact(long a, long b) { + long sum = a + b; + if ((a ^ sum) < 0 && (a ^ b) >= 0) { + throw new ArithmeticException(); + } + return sum; + } + + public static int subtractExact(int a, int b) { + int result = a - b; + if ((a ^ result) < 0 && (a ^ b) < 0) { + throw new ArithmeticException(); + } + return result; + } + + public static long subtractExact(long a, long b) { + long result = a - b; + if ((a ^ result) < 0 && (a ^ b) < 0) { + throw new ArithmeticException(); + } + return result; + } + + public static int multiplyExact(int a, int b) { + if (b == 1) { + return a; + } else if (a == 1) { + return b; + } else if (a == 0 || b == 0) { + return 0; + } + int total = a * b; + if (total / b != a || (a == Integer.MIN_VALUE && b == -1) || (b == Integer.MIN_VALUE && a == -1)) { + throw new ArithmeticException(); + } + return total; + } + + public static long multiplyExact(long a, int b) { + return multiplyExact(a, (long) b); + } + + public static long multiplyExact(long a, long b) { + if (b == 1) { + return a; + } else if (a == 1) { + return b; + } else if (a == 0 || b == 0) { + return 0; + } + long total = a * b; + if (total / b != a || (a == Long.MIN_VALUE && b == -1) || (b == Long.MIN_VALUE && a == -1)) { + throw new ArithmeticException(); + } + return total; + } + + public static int divideExact(int a, int b) { + int q = a / b; + if ((a & b & q) < 0) { // all 3 are negative + throw new ArithmeticException(); + } + return q; + } + + public static long divideExact(long a, long b) { + long q = a / b; + if ((a & b & q) < 0) { // all 3 are negative + throw new ArithmeticException(); + } + return q; + } + @Unmanaged public static double random() { if (PlatformDetector.isC()) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java index ca44db5be..e54208fbc 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java @@ -107,6 +107,94 @@ public final class TStrictMath extends TObject { return TMath.round(a); } + public static int floorDiv(int a, int b) { + return TMath.floorDiv(a, b); + } + + public static long floorDiv(long a, int b) { + return TMath.floorDiv(a, b); + } + + public static long floorDiv(long a, long b) { + return TMath.floorDiv(a, b); + } + + public static int floorMod(int a, int b) { + return TMath.floorMod(a, b); + } + + public static int floorMod(long a, int b) { + return TMath.floorMod(a, b); + } + + public static long floorMod(long a, long b) { + return TMath.floorMod(a, b); + } + + public static int addExact(int a, int b) { + return TMath.addExact(a, b); + } + + public static long addExact(long a, long b) { + return TMath.addExact(a, b); + } + + public static int subtractExact(int a, int b) { + return TMath.subtractExact(a, b); + } + + public static long subtractExact(long a, long b) { + return TMath.subtractExact(a, b); + } + + public static int multiplyExact(int a, int b) { + return TMath.multiplyExact(a, b); + } + + public static long multiplyExact(long a, int b) { + return TMath.multiplyExact(a, b); + } + + public static long multiplyExact(long a, long b) { + return TMath.multiplyExact(a, b); + } + + public static int divideExact(int a, int b) { + return TMath.divideExact(a, b); + } + + public static long divideExact(long a, long b) { + return TMath.divideExact(a, b); + } + + public static int incrementExact(int a) { + return TMath.incrementExact(a); + } + + public static long incrementExact(long a) { + return TMath.incrementExact(a); + } + + public static int decrementExact(int a) { + return TMath.decrementExact(a); + } + + public static long decrementExact(long a) { + return TMath.decrementExact(a); + } + + public static int negateExact(int a) { + return TMath.negateExact(a); + } + + public static long negateExact(long a) { + return TMath.negateExact(a); + } + + public static int toIntExact(long value) { + return TMath.toIntExact(value); + } + public static double random() { return TMath.random(); } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/MathTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/MathTest.java index 659011d25..4d1a1d017 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/MathTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/MathTest.java @@ -16,6 +16,9 @@ package org.teavm.classlib.java.lang; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import java.util.stream.IntStream; +import java.util.stream.LongStream; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.SkipPlatform; @@ -197,4 +200,89 @@ public class MathTest { sameFloat(0.0f, Math.max(-0.0f, 0.0f)); sameFloat(0.0f, Math.max(0.0f, -0.0f)); } + + @Test + public void exacts() { + try { + Math.incrementExact(Integer.MAX_VALUE); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.negateExact(Integer.MIN_VALUE); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.toIntExact((long) Integer.MAX_VALUE + 1); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.addExact(Integer.MAX_VALUE, Integer.MAX_VALUE); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.subtractExact(Integer.MIN_VALUE, 2); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.multiplyExact(Integer.MIN_VALUE, -1); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.multiplyExact(Integer.MIN_VALUE, 2); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.multiplyExact(1 << 30, 2); + fail(); + } catch (ArithmeticException e) { + // ok + } + try { + Math.divideExact(Integer.MIN_VALUE, -1); + fail(); + } catch (ArithmeticException e) { + // ok + } + IntStream.rangeClosed(-10, 10).forEach(x -> { + assertEquals(x + 1, Math.incrementExact(x)); + assertEquals(x - 1, Math.decrementExact(x)); + assertEquals(-x, Math.negateExact(x)); + IntStream.rangeClosed(-10, 10).forEach(y -> { + assertEquals(x + y, Math.addExact(x, y)); + assertEquals(x - y, Math.subtractExact(x, y)); + assertEquals(x * y, Math.multiplyExact(x, y)); + if (y != 0) { + assertEquals(x / y, Math.divideExact(x, y)); + } + }); + }); + LongStream.rangeClosed(-10, 10).forEach(x -> { + assertEquals(x + 1, Math.incrementExact(x)); + assertEquals(x - 1, Math.decrementExact(x)); + assertEquals(-x, Math.negateExact(x)); + assertEquals((int) x, Math.toIntExact(x)); + LongStream.rangeClosed(-10, 10).forEach(y -> { + assertEquals(x + y, Math.addExact(x, y)); + assertEquals(x - y, Math.subtractExact(x, y)); + assertEquals(x * y, Math.multiplyExact(x, y)); + if (y != 0) { + assertEquals(x / y, Math.divideExact(x, y)); + } + }); + }); + } }