classlib: add Math methods for no-overflow addition/multiplication (#859)

Co-authored-by: Jörg Hohwiller <hohwille@users.sourceforge.net>
This commit is contained in:
Ivan Hetman 2023-11-19 19:14:23 +02:00 committed by GitHub
parent 8fa1a86728
commit cd14ece14c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 307 additions and 0 deletions

View File

@ -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()) {

View File

@ -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();
}

View File

@ -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));
}
});
});
}
}