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:
@@ -12,6 +12,7 @@ article; each module's own README has that article's version table, quickstart,
|
|||||||
| [`synchronizers`](synchronizers/) | CountDownLatch vs CyclicBarrier vs Phaser vs Semaphore in Java |
|
| [`synchronizers`](synchronizers/) | CountDownLatch vs CyclicBarrier vs Phaser vs Semaphore in Java |
|
||||||
| [`executors`](executors/) | ExecutorService Done Right: Shutdown, try-with-resources and Virtual Thread Executors |
|
| [`executors`](executors/) | ExecutorService Done Right: Shutdown, try-with-resources and Virtual Thread Executors |
|
||||||
| [`concurrency-interview`](concurrency-interview/) | Top 40 Java Concurrency Interview Questions and Answers (2026) |
|
| [`concurrency-interview`](concurrency-interview/) | Top 40 Java Concurrency Interview Questions and Answers (2026) |
|
||||||
|
| [`arithmetic`](arithmetic/) | Add Two Numbers in Java Without Overflow: addExact, Widening, and BigInteger |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# arithmetic
|
||||||
|
|
||||||
|
Companion code for the ankurm.com post *"Add Two Numbers in Java Without Overflow."* Eighth
|
||||||
|
module in `java-core-examples`.
|
||||||
|
|
||||||
|
`a + b` on two Java `int`s never throws when the true mathematical result doesn't fit - it wraps
|
||||||
|
around silently, two's-complement style. This module reproduces that wraparound with real
|
||||||
|
numbers, then works through every real fix: the `Math` class's `*Exact()` family (which throws
|
||||||
|
`ArithmeticException` instead of wrapping), widening to `long`, `BigInteger` for unbounded
|
||||||
|
precision, and the actual bit trick `Math.addExact` uses internally to detect overflow without
|
||||||
|
doing the addition twice at different widths.
|
||||||
|
|
||||||
|
## Versions this was built and tested against
|
||||||
|
|
||||||
|
| Component | Version | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| JDK | 25.0.4.1+1 (Temurin, LTS) | No preview flags needed - every API used here is finalized. |
|
||||||
|
| JUnit Jupiter | 5.11.0 | Includes a 100,000-iteration randomized cross-check per repeat. |
|
||||||
|
| Maven | 3.9.11 | |
|
||||||
|
| Hardware | 2 vCPU x86-64 VM | Same sandbox as the rest of this series. |
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export JAVA_HOME=/path/to/jdk-25
|
||||||
|
mvn package
|
||||||
|
java -cp target/classes com.ankurm.arithmetic.OverflowWrapDemo
|
||||||
|
java -cp target/classes com.ankurm.arithmetic.ExactArithmeticDemo
|
||||||
|
```
|
||||||
|
|
||||||
|
`scripts/run-all.sh` regenerates every file in `output/` (needs `JDK25_HOME`).
|
||||||
|
|
||||||
|
## What's in here
|
||||||
|
|
||||||
|
| File | What it shows |
|
||||||
|
|---|---|
|
||||||
|
| `.../OverflowWrapDemo.java` | `int` and `long` addition and multiplication wrapping silently past their max values - no exception, just a wrong answer. |
|
||||||
|
| `.../ExactArithmeticDemo.java` | The `Math` `*Exact()` family: `addExact`, `subtractExact`, `multiplyExact`, `incrementExact`, `decrementExact`, `negateExact`, `toIntExact`, `absExact` - normal case and overflow-throws case for each, plus the `Math.abs(Integer.MIN_VALUE)` trap that `absExact` exists to catch. |
|
||||||
|
| `.../WideningAndBigIntegerDemo.java` | Widening to `long` fixes the `int`-range overflow (but `long` can still overflow); `BigInteger` never overflows at all. |
|
||||||
|
| `.../OverflowBitTrickDemo.java` | The XOR-based overflow test for addition, cross-checked against `Math.addExact()` on the same inputs. |
|
||||||
|
| `src/test/.../ArithmeticClaimsTest.java` | Asserts every claim above, including a 100,000-sample-per-repeat randomized cross-check of the bit-trick detector against `BigInteger` ground truth. |
|
||||||
|
| `output/01`-`04` | Each demo's real run. |
|
||||||
|
| `output/05` | JUnit correctness run. |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT - see the [repo-wide LICENSE](../LICENSE).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
$ java OverflowWrapDemo
|
||||||
|
|
||||||
|
=== int addition wraps silently past Integer.MAX_VALUE ===
|
||||||
|
Integer.MAX_VALUE = 2147483647
|
||||||
|
Integer.MAX_VALUE + 1 = -2147483648 (wrapped to Integer.MIN_VALUE, no exception)
|
||||||
|
Integer.MIN_VALUE = -2147483648
|
||||||
|
|
||||||
|
=== A realistic case: summing item prices in cents overflows int ===
|
||||||
|
priceCents * quantity = 1800000000 * 2 = -694967296 (should be 3,600,000,000 - this is wrong, and no exception was thrown)
|
||||||
|
|
||||||
|
=== Multiplication overflows just as silently as addition ===
|
||||||
|
100000 * 100000 = 1410065408 (true value is 10,000,000,000)
|
||||||
|
|
||||||
|
=== long has the same problem, just a much higher ceiling ===
|
||||||
|
Long.MAX_VALUE = 9223372036854775807
|
||||||
|
Long.MAX_VALUE + 1 = -9223372036854775808 (wrapped to Long.MIN_VALUE)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
$ java ExactArithmeticDemo
|
||||||
|
|
||||||
|
=== addExact / subtractExact / multiplyExact: normal case ===
|
||||||
|
Math.addExact(20, 22) = 42
|
||||||
|
Math.subtractExact(50, 8) = 42
|
||||||
|
Math.multiplyExact(6, 7) = 42
|
||||||
|
|
||||||
|
=== addExact / multiplyExact: overflow case - throws instead of wrapping ===
|
||||||
|
Math.addExact(Integer.MAX_VALUE, 1) threw: integer overflow
|
||||||
|
Math.multiplyExact(100000, 100000) threw: integer overflow
|
||||||
|
|
||||||
|
=== incrementExact / decrementExact / negateExact ===
|
||||||
|
Math.incrementExact(41) = 42
|
||||||
|
Math.decrementExact(43) = 42
|
||||||
|
Math.negateExact(-42) = 42
|
||||||
|
Math.incrementExact(Integer.MAX_VALUE) threw: integer overflow
|
||||||
|
|
||||||
|
=== toIntExact: the safe narrowing cast from long back to int ===
|
||||||
|
Math.toIntExact(2000000000L) = 2000000000
|
||||||
|
Math.toIntExact(3000000000L) threw: integer overflow
|
||||||
|
|
||||||
|
=== The classic trap: Math.abs(Integer.MIN_VALUE) does NOT throw, and is wrong ===
|
||||||
|
Math.abs(Integer.MIN_VALUE) = -2147483648 (still negative! +2147483648 doesn't fit in an int)
|
||||||
|
Math.absExact(Integer.MIN_VALUE) threw: Overflow to represent absolute value of Integer.MIN_VALUE
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
$ java WideningAndBigIntegerDemo
|
||||||
|
|
||||||
|
=== Widening to long fixes the int-range overflow from OverflowWrapDemo ===
|
||||||
|
int: Integer.MAX_VALUE + 1 = -2147483648 (wrong)
|
||||||
|
long: (long) Integer.MAX_VALUE + 1 = 2147483648 (correct)
|
||||||
|
|
||||||
|
=== Widening only raises the ceiling - long still overflows eventually ===
|
||||||
|
Long.MAX_VALUE + 1 = -9223372036854775808 (wrapped to Long.MIN_VALUE - same bug, bigger numbers)
|
||||||
|
|
||||||
|
=== BigInteger never overflows - it grows to fit the true value ===
|
||||||
|
BigInteger.valueOf(Long.MAX_VALUE).add(ONE) = 9223372036854775808
|
||||||
|
BigInteger.valueOf(2).pow(100) = 1267650600228229401496703205376
|
||||||
|
(that value has no exact long or int representation at all)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
$ java OverflowBitTrickDemo
|
||||||
|
|
||||||
|
=== Hand-rolled overflow detector vs. known cases ===
|
||||||
|
2000000000 + 2000000000 -> raw int result -294967296, overflow detected: true
|
||||||
|
2000000000 + -1000000000 -> raw int result 1000000000, overflow detected: false
|
||||||
|
2147483647 + 1 -> raw int result -2147483648, overflow detected: true
|
||||||
|
-2147483648 + -1 -> raw int result 2147483647, overflow detected: true
|
||||||
|
100 + 200 -> raw int result 300, overflow detected: false
|
||||||
|
|
||||||
|
=== Cross-checked against Math.addExact() on the same inputs ===
|
||||||
|
2000000000 + 2000000000: bit-trick says overflow=true, Math.addExact() agrees=true
|
||||||
|
2000000000 + -1000000000: bit-trick says overflow=false, Math.addExact() agrees=true
|
||||||
|
2147483647 + 1: bit-trick says overflow=true, Math.addExact() agrees=true
|
||||||
|
-2147483648 + -1: bit-trick says overflow=true, Math.addExact() agrees=true
|
||||||
|
100 + 200: bit-trick says overflow=false, Math.addExact() agrees=true
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Test set: com.ankurm.arithmetic.ArithmeticClaimsTest
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.438 s -- in com.ankurm.arithmetic.ArithmeticClaimsTest
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.ankurm</groupId>
|
||||||
|
<artifactId>java-core-examples</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>arithmetic</artifactId>
|
||||||
|
<name>arithmetic</name>
|
||||||
|
<description>Adding two numbers in Java without silent overflow: the wraparound bug reproduced, the Math *Exact() family, widening to long, BigInteger, and the bit trick Math.addExact itself uses to detect overflow.</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.11.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<release>25</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerates every file in ../output/ from a real run against $JDK25_HOME.
|
||||||
|
# Usage: JDK25_HOME=/path/to/jdk-25 ./run-all.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ -z "${JDK25_HOME:-}" ]; then
|
||||||
|
echo "Set JDK25_HOME to a JDK 25 install first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
OUT="output"
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
"$JDK25_HOME/bin/mvn" -q -f pom.xml package 2>/dev/null || true
|
||||||
|
CP="target/classes"
|
||||||
|
if [ ! -d "$CP" ]; then
|
||||||
|
mkdir -p target/classes
|
||||||
|
"$JDK25_HOME/bin/javac" -d target/classes $(find src/main/java -name '*.java')
|
||||||
|
fi
|
||||||
|
|
||||||
|
run() {
|
||||||
|
local cls="$1" out="$2"
|
||||||
|
echo "\$ java $cls" > "$OUT/$out"
|
||||||
|
echo >> "$OUT/$out"
|
||||||
|
"$JDK25_HOME/bin/java" -cp "$CP" "com.ankurm.arithmetic.$cls" >> "$OUT/$out" 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
run OverflowWrapDemo 01-overflow-wrap.txt
|
||||||
|
run ExactArithmeticDemo 02-exact-arithmetic.txt
|
||||||
|
run WideningAndBigIntegerDemo 03-widening-and-biginteger.txt
|
||||||
|
run OverflowBitTrickDemo 04-overflow-bit-trick.txt
|
||||||
|
|
||||||
|
mkdir -p target/test-classes
|
||||||
|
"$JDK25_HOME/bin/javac" -cp "target/classes:$(find / -name 'junit-jupiter-*.jar' 2>/dev/null | tr '\n' ':')" \
|
||||||
|
-d target/test-classes $(find src/test/java -name '*.java') 2>/dev/null || true
|
||||||
|
|
||||||
|
"$JDK25_HOME/bin/mvn" -q -f pom.xml test > "$OUT/05-correctness-tests.txt" 2>&1 || true
|
||||||
|
cp target/surefire-reports/com.ankurm.arithmetic.ArithmeticClaimsTest.txt "$OUT/05-correctness-tests.txt" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Done. See $OUT/"
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
<module>synchronizers</module>
|
<module>synchronizers</module>
|
||||||
<module>executors</module>
|
<module>executors</module>
|
||||||
<module>concurrency-interview</module>
|
<module>concurrency-interview</module>
|
||||||
|
<module>arithmetic</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
Reference in New Issue
Block a user