Primitives and the arithmetic that surprises you
Integer division, overflow, and floating-point error — three behaviours that are not bugs and are tested every year.
By the end of this chapter you can
- Predict the result of integer division and modulus
- Explain when a cast is needed and where to put it
- Recognise integer overflow
- Say why floating-point comparisons fail
Unit 1 is 15–25% of the exam, and a disproportionate share of that is arithmetic that behaves exactly as specified and not at all as expected.
Integer division truncates
public class Main {
public static void main(String[] args) {
System.out.println("7 / 2 = " + (7 / 2)); // 3, not 3.5
System.out.println("7 % 2 = " + (7 % 2)); // 1, the remainder
System.out.println("-7 / 2 = " + (-7 / 2)); // -3, toward zero
System.out.println("-7 % 2 = " + (-7 % 2)); // -1, sign of the left
System.out.println("7.0 / 2 = " + (7.0 / 2)); // 3.5 — one double is enough
}
}Two integers divided give an integer, and the fraction is discarded, not rounded. $7/2$ is $3$ and $9/2$ is $4$ — both truncate toward zero rather than rounding to nearest.
Negative division truncates toward zero too, so $-7/2$ is $-3$ rather than
$-4$. And % takes the sign of the left operand, which is why -7 % 2 is
$-1$ and not $1$. That is a genuine difference from mathematical modulus and it
appears in questions about even/odd tests on possibly-negative values.
public class Main {
public static void main(String[] args) {
for (int n : new int[]{3, -3}) {
System.out.println(n + ": n%2==1 says " + (n % 2 == 1)
+ ", n%2!=0 says " + (n % 2 != 0));
}
}
}Casting, and where to put it
public class Main {
public static void main(String[] args) {
int a = 7, b = 2;
System.out.println("(double)(a / b) = " + (double)(a / b)); // 3.0 — too late
System.out.println("(double) a / b = " + ((double) a / b)); // 3.5 — correct
System.out.println("a / (double) b = " + (a / (double) b)); // 3.5 — also fine
}
}The first casts the result of an integer division, by which point the
information is gone. Casting either operand first promotes the whole expression
to double.
Casting the other way truncates:
public class Main {
public static void main(String[] args) {
System.out.println("(int) 3.9 = " + (int) 3.9); // 3
System.out.println("(int) -3.9 = " + (int) -3.9); // -3, toward zero
System.out.println("Math.round(3.9) = " + Math.round(3.9));
System.out.println("Math.round(3.4) = " + Math.round(3.4));
}
}(int) truncates; Math.round rounds. A question asking for “the whole number
of…” usually wants truncation, and one asking to “round to the nearest” wants
Math.round.
Overflow wraps silently
public class Main {
public static void main(String[] args) {
int max = Integer.MAX_VALUE;
System.out.println("Integer.MAX_VALUE = " + max);
System.out.println("max + 1 = " + (max + 1));
// Even an intermediate result can overflow before being widened.
int big = 100000;
System.out.println("big * big as int = " + (big * big));
System.out.println("as long = " + ((long) big * big));
}
}max + 1 is the most negative int. No exception, no warning — the arithmetic
wraps around, and the program carries on with a wrong number.
The third line is the version that catches people: big * big is computed as
int arithmetic and then assigned, so widening afterwards is too late. Casting
one operand first, as in the last line, does it properly.
Floating point is approximate
public class Main {
public static void main(String[] args) {
double sum = 0.1 + 0.2;
System.out.println("0.1 + 0.2 = " + sum);
System.out.println("== 0.3 ? " + (sum == 0.3));
System.out.println("difference " + (sum - 0.3));
System.out.println("close enough ? " + (Math.abs(sum - 0.3) < 1e-9));
}
}0.1 and 0.2 have no exact binary representation, any more than $\tfrac13$
has an exact decimal one. The sum is very slightly off, and == notices.
The fix is to compare with a tolerance: Math.abs(a - b) < 0.000001. This is
not a Java defect — every language using binary floating point behaves this way,
and the exam expects you to know it.