arithmetic: companion code for the Add Two Numbers Without Overflow post

Silent int/long overflow reproduced with real numbers, the Math *Exact()
family (addExact/subtractExact/multiplyExact/incrementExact/decrementExact/
negateExact/toIntExact/absExact) with the abs(MIN_VALUE) trap absExact
exists to catch, widening to long vs BigInteger, and the XOR-based overflow
bit trick Math.addExact uses internally, cross-checked against BigInteger
ground truth over 100k random int pairs per JUnit repeat.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FhzLY5p6okFva3qsnsRyvM
This commit is contained in:
2026-09-30 09:16:49 +00:00
co-authored by Claude Sonnet 5
parent 202f2f18a5
commit af615cc17c
15 changed files with 514 additions and 0 deletions
@@ -0,0 +1,66 @@
package com.ankurm.arithmetic;
/**
* The {@code Math} class carries a whole family of "exact" arithmetic methods: every one of them
* does the normal operation when the true mathematical result fits the return type, and throws
* {@link ArithmeticException} instead of silently wrapping when it doesn't. This demo runs the
* addition-relevant members of that family - both the "it just works" case and the "it throws"
* case - plus one genuinely surprising edge case: {@code Math.abs(Integer.MIN_VALUE)} does NOT
* throw and does NOT return a positive number, because positive {@code Integer.MIN_VALUE} has no
* representation in a 32-bit int. {@code Math.absExact} exists specifically to catch that.
*/
public class ExactArithmeticDemo {
public static void main(String[] args) {
System.out.println("=== addExact / subtractExact / multiplyExact: normal case ===");
System.out.println("Math.addExact(20, 22) = " + Math.addExact(20, 22));
System.out.println("Math.subtractExact(50, 8) = " + Math.subtractExact(50, 8));
System.out.println("Math.multiplyExact(6, 7) = " + Math.multiplyExact(6, 7));
System.out.println();
System.out.println("=== addExact / multiplyExact: overflow case - throws instead of wrapping ===");
try {
Math.addExact(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
System.out.println("Math.addExact(Integer.MAX_VALUE, 1) threw: " + e.getMessage());
}
try {
Math.multiplyExact(100_000, 100_000);
} catch (ArithmeticException e) {
System.out.println("Math.multiplyExact(100000, 100000) threw: " + e.getMessage());
}
System.out.println();
System.out.println("=== incrementExact / decrementExact / negateExact ===");
System.out.println("Math.incrementExact(41) = " + Math.incrementExact(41));
System.out.println("Math.decrementExact(43) = " + Math.decrementExact(43));
System.out.println("Math.negateExact(-42) = " + Math.negateExact(-42));
try {
Math.incrementExact(Integer.MAX_VALUE);
} catch (ArithmeticException e) {
System.out.println("Math.incrementExact(Integer.MAX_VALUE) threw: " + e.getMessage());
}
System.out.println();
System.out.println("=== toIntExact: the safe narrowing cast from long back to int ===");
long fitsInInt = 2_000_000_000L;
System.out.println("Math.toIntExact(2000000000L) = " + Math.toIntExact(fitsInInt));
long tooBigForInt = 3_000_000_000L;
try {
Math.toIntExact(tooBigForInt);
} catch (ArithmeticException e) {
System.out.println("Math.toIntExact(3000000000L) threw: " + e.getMessage());
}
System.out.println();
System.out.println("=== The classic trap: Math.abs(Integer.MIN_VALUE) does NOT throw, and is wrong ===");
int minValueAbs = Math.abs(Integer.MIN_VALUE);
System.out.println("Math.abs(Integer.MIN_VALUE) = " + minValueAbs
+ " (still negative! +2147483648 doesn't fit in an int)");
try {
Math.absExact(Integer.MIN_VALUE);
} catch (ArithmeticException e) {
System.out.println("Math.absExact(Integer.MIN_VALUE) threw: " + e.getMessage());
}
}
}
@@ -0,0 +1,60 @@
package com.ankurm.arithmetic;
/**
* How does {@code Math.addExact} actually know overflow happened, without doing the addition
* twice at different widths? A bit trick: for {@code int x + int y = result}, overflow occurred
* if and only if {@code x} and {@code y} have the same sign AND that sign differs from
* {@code result}'s sign. Two numbers with the same sign can only overflow into the opposite
* sign; two numbers with different signs can never overflow (their sum is always between them).
*
* <p>Expressed without any branching on sign:
* {@code overflow = ((x ^ result) & (y ^ result)) < 0}. XOR-ing two ints and checking the sign
* bit of the result is a cheap way to ask "do these differ in sign?" - the expression is true
* (negative, i.e. the top bit is set) exactly when both {@code x} and {@code y} differ in sign
* from {@code result}, which is exactly the overflow condition above. This is not a novel trick -
* it is functionally the same check the JDK's own {@code Math.addExact} source uses internally.
*/
public class OverflowBitTrickDemo {
/** Returns true if x + y overflows the int range, computed without widening to long. */
static boolean additionOverflows(int x, int y) {
int result = x + y; // allowed to wrap; we only use it to test the sign relationship
return ((x ^ result) & (y ^ result)) < 0;
}
public static void main(String[] args) {
System.out.println("=== Hand-rolled overflow detector vs. known cases ===");
report(2_000_000_000, 2_000_000_000); // same sign, overflows
report(2_000_000_000, -1_000_000_000); // different signs, never overflows
report(Integer.MAX_VALUE, 1); // classic edge case
report(Integer.MIN_VALUE, -1); // negative-side overflow
report(100, 200); // ordinary case, no overflow
System.out.println();
System.out.println("=== Cross-checked against Math.addExact() on the same inputs ===");
int[][] cases = {
{2_000_000_000, 2_000_000_000},
{2_000_000_000, -1_000_000_000},
{Integer.MAX_VALUE, 1},
{Integer.MIN_VALUE, -1},
{100, 200}
};
for (int[] c : cases) {
boolean detected = additionOverflows(c[0], c[1]);
boolean addExactThrew;
try {
Math.addExact(c[0], c[1]);
addExactThrew = false;
} catch (ArithmeticException e) {
addExactThrew = true;
}
System.out.println(c[0] + " + " + c[1] + ": bit-trick says overflow=" + detected
+ ", Math.addExact() agrees=" + (detected == addExactThrew));
}
}
private static void report(int x, int y) {
boolean overflow = additionOverflows(x, y);
System.out.println(x + " + " + y + " -> raw int result " + (x + y) + ", overflow detected: " + overflow);
}
}
@@ -0,0 +1,42 @@
package com.ankurm.arithmetic;
/**
* Java's {@code int} and {@code long} arithmetic never throws on overflow. It wraps around
* silently, two's-complement style, and the compiler and JVM both consider {@code a + b} on
* two ints to be perfectly well-defined behavior even when the mathematically correct result
* doesn't fit. This demo reproduces that wraparound with real numbers instead of just quoting
* {@code Integer.MAX_VALUE}.
*/
public class OverflowWrapDemo {
public static void main(String[] args) {
System.out.println("=== int addition wraps silently past Integer.MAX_VALUE ===");
int max = Integer.MAX_VALUE;
int wrapped = max + 1;
System.out.println("Integer.MAX_VALUE = " + max);
System.out.println("Integer.MAX_VALUE + 1 = " + wrapped + " (wrapped to Integer.MIN_VALUE, no exception)");
System.out.println("Integer.MIN_VALUE = " + Integer.MIN_VALUE);
System.out.println();
System.out.println("=== A realistic case: summing item prices in cents overflows int ===");
int priceCents = 1_800_000_000; // $18,000,000.00, a plausible large invoice line in cents
int quantity = 2;
int totalCents = priceCents * quantity;
System.out.println("priceCents * quantity = " + priceCents + " * " + quantity + " = " + totalCents
+ " (should be 3,600,000,000 - this is wrong, and no exception was thrown)");
System.out.println();
System.out.println("=== Multiplication overflows just as silently as addition ===");
int a = 100_000;
int b = 100_000;
int product = a * b; // mathematically 10,000,000,000 - doesn't fit in an int
System.out.println(a + " * " + b + " = " + product + " (true value is 10,000,000,000)");
System.out.println();
System.out.println("=== long has the same problem, just a much higher ceiling ===");
long longMax = Long.MAX_VALUE;
long longWrapped = longMax + 1;
System.out.println("Long.MAX_VALUE = " + longMax);
System.out.println("Long.MAX_VALUE + 1 = " + longWrapped + " (wrapped to Long.MIN_VALUE)");
}
}
@@ -0,0 +1,35 @@
package com.ankurm.arithmetic;
import java.math.BigInteger;
/**
* Two more ways to avoid the overflow in {@link OverflowWrapDemo}: widen to {@code long} before
* adding (cheap, but only pushes the ceiling higher - it can still overflow), or switch to
* {@link BigInteger} for genuinely unbounded precision (more expensive, never overflows).
*/
public class WideningAndBigIntegerDemo {
public static void main(String[] args) {
System.out.println("=== Widening to long fixes the int-range overflow from OverflowWrapDemo ===");
int a = Integer.MAX_VALUE;
int bad = a + 1; // wraps, wrong
long good = (long) a + 1; // widened before the add, correct
System.out.println("int: Integer.MAX_VALUE + 1 = " + bad + " (wrong)");
System.out.println("long: (long) Integer.MAX_VALUE + 1 = " + good + " (correct)");
System.out.println();
System.out.println("=== Widening only raises the ceiling - long still overflows eventually ===");
long lmax = Long.MAX_VALUE;
long stillWraps = lmax + 1;
System.out.println("Long.MAX_VALUE + 1 = " + stillWraps + " (wrapped to Long.MIN_VALUE - same bug, bigger numbers)");
System.out.println();
System.out.println("=== BigInteger never overflows - it grows to fit the true value ===");
BigInteger big = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
System.out.println("BigInteger.valueOf(Long.MAX_VALUE).add(ONE) = " + big);
BigInteger huge = BigInteger.valueOf(2).pow(100);
System.out.println("BigInteger.valueOf(2).pow(100) = " + huge);
System.out.println("(that value has no exact long or int representation at all)");
}
}
@@ -0,0 +1,106 @@
package com.ankurm.arithmetic;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import java.math.BigInteger;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ArithmeticClaimsTest {
@Test
void plainIntAdditionWrapsPastMaxValueInsteadOfThrowing() {
int wrapped = Integer.MAX_VALUE + 1;
assertEquals(Integer.MIN_VALUE, wrapped);
}
@Test
void addExactThrowsExactlyWhenTheTrueSumDoesNotFitAnInt() {
assertEquals(42, Math.addExact(20, 22));
assertThrows(ArithmeticException.class, () -> Math.addExact(Integer.MAX_VALUE, 1));
}
@Test
void multiplyExactThrowsExactlyWhenTheTrueProductDoesNotFitAnInt() {
assertEquals(42, Math.multiplyExact(6, 7));
assertThrows(ArithmeticException.class, () -> Math.multiplyExact(100_000, 100_000));
}
@Test
void absDoesNotThrowOnIntegerMinValueAndReturnsAWrongNegativeNumber() {
// Math.abs is NOT part of the exact family - this is the trap absExact exists to catch.
assertEquals(Integer.MIN_VALUE, Math.abs(Integer.MIN_VALUE));
}
@Test
void absExactThrowsOnIntegerMinValueInsteadOfReturningAWrongAnswer() {
assertThrows(ArithmeticException.class, () -> Math.absExact(Integer.MIN_VALUE));
}
@Test
void toIntExactThrowsWhenALongDoesNotFitInAnInt() {
assertEquals(2_000_000_000, Math.toIntExact(2_000_000_000L));
assertThrows(ArithmeticException.class, () -> Math.toIntExact(3_000_000_000L));
}
@Test
void wideningToLongBeforeAddingAvoidsTheIntRangeOverflow() {
int a = Integer.MAX_VALUE;
long widened = (long) a + 1;
assertEquals(2_147_483_648L, widened);
}
@Test
void bigIntegerNeverOverflowsRegardlessOfMagnitude() {
BigInteger huge = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
assertEquals(new BigInteger("9223372036854775808"), huge);
}
@RepeatedTest(5)
void bitTrickOverflowDetectorAgreesWithBigIntegerGroundTruthOnRandomPairs() {
Random random = new Random();
for (int i = 0; i < 100_000; i++) {
int x = random.nextInt();
int y = random.nextInt();
boolean detected = OverflowBitTrickDemo.additionOverflows(x, y);
BigInteger trueSum = BigInteger.valueOf(x).add(BigInteger.valueOf(y));
boolean actuallyOverflows = trueSum.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0
|| trueSum.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0;
assertEquals(actuallyOverflows, detected,
() -> x + " + " + y + ": bit trick said overflow=" + detected
+ " but true sum is " + trueSum);
}
}
@Test
void bitTrickAgreesWithMathAddExactOnKnownEdgeCases() {
int[][] cases = {
{2_000_000_000, 2_000_000_000},
{2_000_000_000, -1_000_000_000},
{Integer.MAX_VALUE, 1},
{Integer.MIN_VALUE, -1},
{Integer.MIN_VALUE, Integer.MIN_VALUE},
{100, 200},
{0, 0}
};
for (int[] c : cases) {
boolean detected = OverflowBitTrickDemo.additionOverflows(c[0], c[1]);
boolean addExactThrew = true;
try {
Math.addExact(c[0], c[1]);
addExactThrew = false;
} catch (ArithmeticException ignored) {
// expected on overflow
}
assertTrue(detected == addExactThrew,
c[0] + " + " + c[1] + ": bit trick=" + detected + " vs addExact threw=" + addExactThrew);
}
}
}