Math Operations — Find the Bug¶
Practice finding and fixing bugs in Java code related to Math Operations. Each exercise contains buggy code — your job is to find the bug, explain why it happens, and fix it.
How to Use¶
- Read the buggy code carefully
- Try to find the bug without looking at the hint
- Write the fix yourself before checking the solution
- Understand why the bug happens — not just how to fix it
Difficulty Levels¶
| Level | Description |
|---|---|
| Easy | Common beginner mistakes — integer overflow, basic operator precedence, division truncation |
| Medium | BigDecimal misuse, modulo with negatives, floating-point comparison traps |
| Hard | Math.abs edge cases, overflow in midpoint calculation, accumulated rounding in financial math |
Bug 1: Integer Overflow in Multiplication Easy¶
What the code should do: Calculate the total number of milliseconds in 50 days.
public class Main {
public static void main(String[] args) {
int days = 50;
int hoursPerDay = 24;
int minutesPerHour = 60;
int secondsPerMinute = 60;
int millisecondsPerSecond = 1000;
int totalMillis = days * hoursPerDay * minutesPerHour
* secondsPerMinute * millisecondsPerSecond;
System.out.println("Milliseconds in " + days + " days: " + totalMillis);
}
}
Expected output:
Actual output:
Hint
All variables are `int`. The result `50 * 24 * 60 * 60 * 1000 = 4,320,000,000` — is that within the range of `Integer.MAX_VALUE` (2,147,483,647)?Bug Explanation
**Bug:** Integer overflow. The result `4,320,000,000` exceeds `Integer.MAX_VALUE` (2,147,483,647). Java performs all arithmetic in `int` since every operand is `int`, and the multiplication wraps around silently. **Why it happens:** Java does not throw an exception on integer overflow. The 32-bit result wraps via two's complement arithmetic (JLS 15.17.1). **Impact:** The computed value is completely wrong — a small positive number instead of 4.3 billion.Fixed Code
public class Main {
public static void main(String[] args) {
long days = 50;
long hoursPerDay = 24;
long minutesPerHour = 60;
long secondsPerMinute = 60;
long millisecondsPerSecond = 1000;
long totalMillis = days * hoursPerDay * minutesPerHour
* secondsPerMinute * millisecondsPerSecond;
System.out.println("Milliseconds in " + days + " days: " + totalMillis);
// Output: Milliseconds in 50 days: 4320000000
}
}
Bug 2: Floating-Point Equality Comparison Easy¶
What the code should do: Check if a shopping cart total equals $1.00 after adding ten items at $0.10 each.
public class Main {
public static void main(String[] args) {
double cartTotal = 0.0;
for (int i = 0; i < 10; i++) {
cartTotal += 0.1;
}
if (cartTotal == 1.0) {
System.out.println("Cart total is $1.00 — proceed to checkout");
} else {
System.out.println("Cart total is NOT $1.00: " + cartTotal);
}
}
}
Expected output:
Actual output:
Hint
Can `0.1` be represented exactly in IEEE 754 binary floating-point? What happens when you add an inexact value ten times?Bug Explanation
**Bug:** `0.1` cannot be represented exactly in IEEE 754 double-precision. Each addition introduces a small rounding error, and after 10 iterations the accumulated error makes the total slightly less than `1.0`. **Why it happens:** Binary floating-point can only represent fractions whose denominators are powers of 2. `0.1 = 1/10` has a non-terminating binary representation. **Impact:** The `==` comparison fails even though the values are mathematically equal. This can break financial logic, threshold checks, and conditional branches.Fixed Code
public class Main {
private static final double EPSILON = 1e-9;
public static void main(String[] args) {
double cartTotal = 0.0;
for (int i = 0; i < 10; i++) {
cartTotal += 0.1;
}
// Option 1: Epsilon comparison
if (Math.abs(cartTotal - 1.0) < EPSILON) {
System.out.println("Cart total is $1.00 — proceed to checkout");
}
// Option 2: Use BigDecimal for exact decimal arithmetic
// BigDecimal total = BigDecimal.ZERO;
// BigDecimal increment = new BigDecimal("0.1");
// for (int i = 0; i < 10; i++) total = total.add(increment);
// if (total.compareTo(BigDecimal.ONE) == 0) { ... }
}
}
Bug 3: Integer Division Truncation Easy¶
What the code should do: Calculate a student's score as a percentage.
public class Main {
public static void main(String[] args) {
int correct = 17;
int total = 20;
double percentage = correct / total * 100;
System.out.println("Score: " + percentage + "%");
}
}
Expected output:
Actual output:
Hint
What is the result of `17 / 20` when both operands are `int`? The division happens before the multiplication by 100.Bug Explanation
**Bug:** `correct / total` is integer division: `17 / 20 = 0`. Then `0 * 100 = 0`, which is widened to `double` `0.0`. The fractional part is discarded before multiplication. **Why it happens:** Java uses integer arithmetic when both operands are `int` (JLS 15.17.2). The result is truncated toward zero. **Impact:** The percentage is always `0.0%` for any score less than 100%.Fixed Code
**What changed:** Cast `correct` to `double` before division so that floating-point division is performed instead of integer division.Bug 4: Division by Zero — NaN Propagation Medium¶
What the code should do: Calculate speed and apply a bonus based on the result.
public class Main {
public static void main(String[] args) {
double distance = 0.0;
double time = 0.0;
double speed = distance / time;
System.out.println("Speed: " + speed);
// NaN propagates silently through all calculations
double bonus = speed + 50;
if (bonus > 100) {
System.out.println("High bonus!");
} else if (bonus <= 100) {
System.out.println("Normal bonus");
} else {
System.out.println("This should never print... right?");
}
}
}
Expected output:
Actual output:
Hint
`NaN + 50` is `NaN`. What does `NaN > 100` return? What does `NaN <= 100` return? All comparisons involving NaN return the same value.Bug Explanation
**Bug:** Any comparison involving `NaN` returns `false` — including `NaN > 100`, `NaN <= 100`, and `NaN == NaN`. So both the `if` and `else if` are `false`, and execution falls through to the `else` block. **Why it happens:** IEEE 754 specifies that NaN is unordered. All relational comparisons with NaN yield `false` (JLS 15.20.1). **Impact:** NaN propagates silently through calculations and defeats all conditional checks, leading to unreachable-looking code branches being executed.Fixed Code
public class Main {
public static void main(String[] args) {
double distance = 0.0;
double time = 0.0;
double speed = distance / time;
// Always check for NaN before using the result
if (Double.isNaN(speed)) {
System.out.println("Invalid speed calculation (0/0)");
speed = 0.0; // use a safe default
} else if (Double.isInfinite(speed)) {
System.out.println("Invalid speed calculation (division by zero)");
speed = 0.0;
}
double bonus = speed + 50;
if (bonus > 100) {
System.out.println("High bonus!");
} else {
System.out.println("Normal bonus");
}
}
}
Bug 5: Modulo with Negative Numbers Medium¶
What the code should do: Map any integer offset to a valid array index (0 to 6) for a circular buffer.
public class Main {
public static void main(String[] args) {
String[] days = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
int[] offsets = {0, 3, 7, -1, -8, 14, -14};
for (int offset : offsets) {
int index = offset % 7;
System.out.println("Offset " + offset + " -> " + days[index]);
}
}
}
Expected output:
Offset 0 -> Sun
Offset 3 -> Wed
Offset 7 -> Sun
Offset -1 -> Sat
Offset -8 -> Sat
Offset 14 -> Sun
Offset -14 -> Sun
Actual output:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 7
Hint
In Java, `(-1) % 7` returns `-1`, not `6`. The `%` operator preserves the sign of the dividend.Bug Explanation
**Bug:** Java's `%` operator returns a result with the same sign as the dividend. `-1 % 7 = -1`, `-8 % 7 = -1`. Negative indices cause `ArrayIndexOutOfBoundsException`. **Why it happens:** JLS 15.17.3 defines the remainder operation such that `(a/b)*b + (a%b) == a`. Since `-1/7 = 0` in integer division, `-1 % 7 = -1`. **Impact:** Any negative offset crashes the program.Fixed Code
public class Main {
public static void main(String[] args) {
String[] days = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
int[] offsets = {0, 3, 7, -1, -8, 14, -14};
for (int offset : offsets) {
// Option 1: Manual fix — always non-negative
int index = ((offset % 7) + 7) % 7;
// Option 2: Math.floorMod (Java 8+)
// int index = Math.floorMod(offset, 7);
System.out.println("Offset " + offset + " -> " + days[index]);
}
}
}
Bug 6: BigDecimal Created from double Medium¶
What the code should do: Calculate the total price of 3 items at $19.99 each with exact precision.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal price = new BigDecimal(19.99);
BigDecimal quantity = new BigDecimal(3);
BigDecimal total = price.multiply(quantity);
System.out.println("Unit price: $" + price);
System.out.println("Total: $" + total);
}
}
Expected output:
Actual output:
Unit price: $19.989999999999998437694739946164190769195556640625
Total: $59.969999999999995313084219838492572307586669921875
Hint
What is the difference between `new BigDecimal(19.99)` and `new BigDecimal("19.99")`? The `double` constructor captures the exact IEEE 754 binary representation.Bug Explanation
**Bug:** `new BigDecimal(19.99)` takes the exact binary representation of the `double` literal `19.99`, which is `19.989999999999998437...`. The imprecision is baked into the BigDecimal. **Why it happens:** The `double` value `19.99` is already imprecise before the `BigDecimal` constructor sees it. The constructor faithfully records that imprecise value. **Impact:** All subsequent calculations carry and amplify the error. Financial reports show wrong totals.Fixed Code
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
// Use String constructor for exact decimal values
BigDecimal price = new BigDecimal("19.99");
BigDecimal quantity = new BigDecimal("3");
BigDecimal total = price.multiply(quantity);
System.out.println("Unit price: $" + price); // $19.99
System.out.println("Total: $" + total); // $59.97
// Alternative: BigDecimal.valueOf(19.99) also works
// because it uses Double.toString(19.99) internally
}
}
Bug 7: BigDecimal Division Without Rounding Mode Medium¶
What the code should do: Split a bill of $100 equally among 3 people.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
BigDecimal total = new BigDecimal("100.00");
BigDecimal people = new BigDecimal("3");
BigDecimal share = total.divide(people);
System.out.println("Each person pays: $" + share);
}
}
Expected output:
Actual output:
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
Hint
`100 / 3 = 33.333...` is a non-terminating decimal. `BigDecimal` refuses to produce an inexact result unless you explicitly tell it how to round.Bug Explanation
**Bug:** `BigDecimal.divide()` without a rounding mode throws `ArithmeticException` when the result is a non-terminating decimal. Unlike `double` which silently truncates, `BigDecimal` requires explicit precision decisions. **Why it happens:** `BigDecimal` is designed for exact arithmetic. When an exact result is impossible, it refuses to guess how to round. **Impact:** Runtime crash for any division that produces an infinite decimal (1/3, 1/7, 1/6, etc.).Fixed Code
import java.math.BigDecimal;
import java.math.RoundingMode;
public class Main {
public static void main(String[] args) {
BigDecimal total = new BigDecimal("100.00");
BigDecimal people = new BigDecimal("3");
// Specify scale (decimal places) and rounding mode
BigDecimal share = total.divide(people, 2, RoundingMode.HALF_UP);
System.out.println("Each person pays: $" + share); // $33.33
// Verify: 33.33 * 3 = 99.99 — one cent short!
BigDecimal remainder = total.subtract(share.multiply(people));
System.out.println("Remainder: $" + remainder); // $0.01
System.out.println("Last person pays: $" + share.add(remainder)); // $33.34
}
}
Bug 8: Math.round Edge Cases with float Precision Hard¶
What the code should do: Round values and convert between int and float without precision loss.
public class Main {
public static void main(String[] args) {
// Bug 1: float cannot represent 16777217 exactly
float value = 16777217f;
int rounded = Math.round(value);
System.out.println("round(16777217f) = " + rounded);
// Bug 2: implicit int-to-float widening loses precision
int bigNumber = 123456789;
float asFloat = bigNumber;
System.out.println("Original int: " + bigNumber);
System.out.println("As float: " + asFloat);
System.out.println("Back to int: " + (int) asFloat);
// Bug 3: Math.round(-0.5f) does not round to -1
System.out.println("round(-0.5f) = " + Math.round(-0.5f));
System.out.println("round(0.5f) = " + Math.round(0.5f));
}
}
Expected output:
round(16777217f) = 16777217
Original int: 123456789
As float: 1.23456789E8
Back to int: 123456789
round(-0.5f) = -1
round(0.5f) = 1
Actual output:
round(16777217f) = 16777216
Original int: 123456789
As float: 1.23456792E8
Back to int: 123456792
round(-0.5f) = 0
round(0.5f) = 1
Hint
A `float` has only 24 bits of mantissa (~7 decimal digits of precision). What happens when you assign a large `int` to a `float`? Also, `Math.round` uses `floor(a + 0.5)` semantics — check what happens at exactly `-0.5`.Bug Explanation
**Bug:** Three separate precision issues: 1. `float` cannot represent `16777217` exactly (2^24 + 1 exceeds mantissa precision), so it rounds to `16777216`. 2. Widening `int` to `float` silently loses precision for values > 2^24. Java allows this widening without a cast (JLS 5.1.2). 3. `Math.round(-0.5f)` returns `0` (not `-1`) because the implementation adds 0.5 and floors: `floor(-0.5 + 0.5) = floor(0.0) = 0`. **Why it happens:** IEEE 754 single-precision has only 23 explicit mantissa bits (24 with implicit leading 1). JLS 5.1.2 allows widening from `int` to `float` despite potential precision loss. **Impact:** Subtle numerical errors in graphics, scientific computing, and financial calculations.Fixed Code
public class Main {
public static void main(String[] args) {
// Fix 1: Use double instead of float for large values
double value = 16777217.0;
long rounded = Math.round(value);
System.out.println("round(16777217.0) = " + rounded); // 16777217
// Fix 2: Use double for int-to-floating-point conversion
int bigNumber = 123456789;
double asDouble = bigNumber; // double has 52-bit mantissa — safe for all int values
System.out.println("Original int: " + bigNumber);
System.out.println("As double: " + asDouble);
System.out.println("Back to int: " + (int) asDouble); // 123456789
// Fix 3: Be aware of Math.round asymmetry at -0.5
// For symmetric rounding, use custom logic
System.out.println("custom(-0.5) = " + roundAwayFromZero(-0.5)); // -1
System.out.println("custom(0.5) = " + roundAwayFromZero(0.5)); // 1
}
static long roundAwayFromZero(double value) {
return (long) (value >= 0 ? Math.floor(value + 0.5) : Math.ceil(value - 0.5));
}
}
Bug 9: Overflow in Midpoint Calculation Hard¶
What the code should do: Calculate the midpoint of two integer values without overflow (classic binary search bug).
public class Main {
public static int binarySearch(int[] sorted, int target) {
int low = 0;
int high = sorted.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (sorted[mid] == target) return mid;
else if (sorted[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
public static void main(String[] args) {
// Demonstrate the overflow
int low = Integer.MAX_VALUE - 2; // 2147483645
int high = Integer.MAX_VALUE; // 2147483647
int mid = (low + high) / 2;
System.out.println("Midpoint: " + mid);
}
}
Expected output:
Actual output:
Hint
This is the famous bug discovered by Joshua Bloch in `java.util.Arrays.binarySearch`. It existed in the JDK from 1997 to 2006. `low + high` overflows when both are large positive integers.Bug Explanation
**Bug:** `low + high` overflows `Integer.MAX_VALUE` when both values are large. `2147483645 + 2147483647 = 4294967292`, which wraps to `-4` in two's complement. Then `-4 / 2 = -2`, which is a negative index. **Why it happens:** Java does not detect integer overflow (JLS 15.18.2). This bug existed in the JDK's `Arrays.binarySearch` implementation for nearly a decade (bug 5045582). **Impact:** Binary search produces wrong results or throws `ArrayIndexOutOfBoundsException` for arrays with more than ~1 billion elements.Fixed Code
public class Main {
public static int binarySearch(int[] sorted, int target) {
int low = 0;
int high = sorted.length - 1;
while (low <= high) {
// Fix: overflow-safe midpoint calculation
int mid = low + (high - low) / 2;
// Alternative: int mid = (low + high) >>> 1; // unsigned right shift
if (sorted[mid] == target) return mid;
else if (sorted[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
public static void main(String[] args) {
int low = Integer.MAX_VALUE - 2;
int high = Integer.MAX_VALUE;
int mid = low + (high - low) / 2;
System.out.println("Midpoint: " + mid); // 2147483646
}
}
Bug 10: Math.abs Returns Negative for Integer.MIN_VALUE Hard¶
What the code should do: Compute the absolute difference between two integers safely.
public class Main {
public static int absoluteDifference(int a, int b) {
return Math.abs(a - b);
}
public static void main(String[] args) {
System.out.println(absoluteDifference(10, 5));
System.out.println(absoluteDifference(-5, 10));
System.out.println(absoluteDifference(Integer.MIN_VALUE, 0));
System.out.println(absoluteDifference(Integer.MIN_VALUE, 1));
// Direct demonstration
System.out.println("Math.abs(Integer.MIN_VALUE) = " + Math.abs(Integer.MIN_VALUE));
}
}
Expected output:
Actual output:
Hint
`Integer.MIN_VALUE` is `-2147483648` but `Integer.MAX_VALUE` is `2147483647`. There is no positive `int` that equals `2147483648`. What does `Math.abs` return when the result does not fit?Bug Explanation
**Bug:** `Math.abs(Integer.MIN_VALUE)` returns `Integer.MIN_VALUE` — a negative number. The two's complement negation of `-2147483648` is `2147483648`, which overflows back to `-2147483648`. Additionally, `Integer.MIN_VALUE - 1` overflows to `Integer.MAX_VALUE`, giving `Math.abs(Integer.MAX_VALUE) = 2147483647` instead of the correct `2147483649`. **Why it happens:** JLS 15.15.4 specifies that unary minus of `Integer.MIN_VALUE` overflows. `Math.abs` is defined as `(a < 0) ? -a : a`, and `-Integer.MIN_VALUE` overflows to `Integer.MIN_VALUE`. **Impact:** Any code assuming `Math.abs()` always returns a non-negative value can produce negative array indices, incorrect sort orders, or wrong distance calculations. **JVM spec reference:** JLS 15.15.4, Javadoc for `Math.abs(int)`.Fixed Code
public class Main {
public static long absoluteDifference(int a, int b) {
// Widen to long BEFORE subtraction to avoid overflow
return Math.abs((long) a - (long) b);
}
public static void main(String[] args) {
System.out.println(absoluteDifference(10, 5)); // 5
System.out.println(absoluteDifference(-5, 10)); // 15
System.out.println(absoluteDifference(Integer.MIN_VALUE, 0)); // 2147483648
System.out.println(absoluteDifference(Integer.MIN_VALUE, 1)); // 2147483649
// Java 15+: Math.absExact throws ArithmeticException on overflow
// Math.absExact(Integer.MIN_VALUE) -> ArithmeticException
}
}
Score Card¶
Track your progress:
| Bug | Difficulty | Found without hint? | Understood why? | Fixed correctly? |
|---|---|---|---|---|
| 1 | Easy | ☐ | ☐ | ☐ |
| 2 | Easy | ☐ | ☐ | ☐ |
| 3 | Easy | ☐ | ☐ | ☐ |
| 4 | Medium | ☐ | ☐ | ☐ |
| 5 | Medium | ☐ | ☐ | ☐ |
| 6 | Medium | ☐ | ☐ | ☐ |
| 7 | Medium | ☐ | ☐ | ☐ |
| 8 | Hard | ☐ | ☐ | ☐ |
| 9 | Hard | ☐ | ☐ | ☐ |
| 10 | Hard | ☐ | ☐ | ☐ |
Rating:¶
- 10/10 without hints — Senior-level Java math debugging skills
- 7-9/10 — Solid middle-level understanding
- 4-6/10 — Good junior, keep practicing
- < 4/10 — Review the topic fundamentals first