JAVA / OPERATORS
Arithmetic operators and expression evaluation
Predict and control the result of Java arithmetic expressions: integer division, remainder signs, numeric promotion, overflow, and division by zero.
What you will learn
- Spot integer division by checking operand types, not the type you assign into
- Get fractions from ints with a cast on one operand: (double) sum / count
- Explain why -7 % 2 is -1 and why 1000000 * 1000000 is negative in int math
- Predict when arithmetic throws (5 / 0) and when it yields Infinity or NaN (5.0 / 0)
Understanding Arithmetic operators and expression evaluation
Java has five binary arithmetic operators, `+`, `-`, `*`, `/` and `%`, plus unary `-` and `+`. Each of them is really a family of machine operations, and the operand types choose which member of the family runs. `9 / 4` is integer division and gives 2 because both operands are `int`; `9 / 4.0` is floating-point division and gives 2.25 because one operand is a `double`. The variable you store the answer in gets no vote in that decision.
An expression is evaluated from the inside out: every operand is first reduced to a single value, then the operator combines those values. Before combining, Java applies binary numeric promotion, so `byte`, `short` and `char` operands become `int`, and if either side is `long`, `float` or `double` both sides widen to the wider of the two. That is why `'A' + 1` is the int 66, and why `byte + byte` produces an `int` that will not fit back into a `byte` without a cast. Conversion to the declared type happens last, after the right-hand side already has a value, which is why `double avg = 7 / 2;` stores 3.0: the fraction was discarded one step earlier.
Integer arithmetic is exact but bounded. An `int` is 32 bits, so `1_000_000 * 1_000_000` silently wraps to -727379968 instead of a trillion, and casting one operand to `long` before multiplying is what prevents it. Integer `/` and `%` with a zero divisor throw `ArithmeticException` because no `int` value could stand for the answer, while `double` has such values, so `5.0 / 0` is `Infinity` and `0.0 / 0.0` is `NaN` with no exception. The sign rule for `%` follows from division truncating toward zero: since `-7 / 2` is -3, the remainder must be -1 so that `(a / b) * b + a % b` still rebuilds `a`.
public class Arithmetic {
public static void main(String[] args) {
int totalMinutes = 197;
int hours = totalMinutes / 60;
int minutes = totalMinutes % 60;
System.out.println(hours + "h " + minutes + "m");
System.out.println(7 / 2);
System.out.println(7 % 2);
System.out.println(-7 / 2);
System.out.println(-7 % 2);
System.out.println(7 / 2.0);
System.out.println((double) 7 / 2);
int million = 1_000_000;
System.out.println(million * million);
System.out.println((long) million * million);
}
}An arithmetic operator's behaviour and result type are fixed by the types of its own operands, not by the variable that receives the answer.
Worked examples
When + is not addition
Shows how a String operand turns the rest of a left-to-right chain of + into concatenation.
public class ConcatOrder {
public static void main(String[] args) {
int a = 2;
int b = 3;
System.out.println("sum: " + a + b);
System.out.println("sum: " + (a + b));
System.out.println(a + b + " is the total");
System.out.println(1 + 2 + "3" + 4 + 5);
}
}Example explained
Line 1The first line groups as (("sum: " + a) + b), and one String operand makes each + a concatenation, so 2 and 3 are appended as digits.
Line 2Parentheses give a + b its own subexpression with two int operands, so it adds to 5 before the concatenation happens.
Line 3On the third line the leftmost + still sees two ints, so numeric addition runs before any String enters the chain.
Line 4The last line switches meaning mid-expression: 1 + 2 is addition, and every + after the String "3" concatenates, producing 3345.
Promotion and the width of the result
Demonstrates that small integer types widen to int and that float and double differ in how much of a fraction they keep.
public class Promotion {
public static void main(String[] args) {
byte b1 = 100;
byte b2 = 27;
int sum = b1 + b2;
System.out.println(sum);
char letter = 'A';
System.out.println(letter + 1);
System.out.println((char) (letter + 1));
System.out.println(1.0f / 3);
System.out.println(1.0 / 3);
System.out.println(0.1 + 0.2);
}
}Example explained
Line 1b1 + b2 is computed as int, so the sum must be stored in an int even though 127 would fit in a byte.
Line 2letter + 1 promotes the char to its code 65 and prints the int 66; casting back to char reinterprets 66 as the letter B.
Line 31.0f / 3 is float division with roughly 7 significant digits, while 1.0 / 3 is double division with about 16.
Line 40.1 and 0.2 have no exact binary form, so their sum is the nearest representable double, slightly above 0.3.
Zero divisors: throw or Infinity
Contrasts integer division by zero, which throws, with floating-point division by zero, which produces Infinity or NaN.
public class ZeroDivisor {
public static void main(String[] args) {
System.out.println(5.0 / 0);
System.out.println(-5.0 / 0);
double nan = 0.0 / 0.0;
System.out.println(nan);
System.out.println(nan + 1);
int n = 5;
int d = 0;
try {
System.out.println(n / d);
} catch (ArithmeticException e) {
System.out.println("caught: " + e.getMessage());
}
}
}Example explained
Line 1In 5.0 / 0 the int 0 is promoted to 0.0, and double division defines a signed infinity for a nonzero numerator over zero.
Line 20.0 / 0.0 has no defensible value, so the result is NaN, and nan + 1 stays NaN: one undefined step contaminates every later one.
Line 3n / d has two int operands and no int result exists, so the JVM throws ArithmeticException with the message / by zero.
Line 4The same written operator therefore either throws or returns Infinity, decided purely by operand types.
Important notes
double and float store binary fractions, so results like 0.1 + 0.2 are slightly off and errors accumulate; use long cents or BigDecimal for money.
Integer +, - and * never report overflow; if you want a failure instead of a wrapped value, use Math.addExact or Math.multiplyExact, which throw ArithmeticException.
Common mistakes
Writing double average = total / count; with two ints: the division truncates first, so 7 / 2 stores 3.0 and no change to the variable's type can recover 3.5.
Writing System.out.println("total: " + a + b) and expecting a sum: the leading String makes both + concatenations, printing total: 23 instead of total: 5.
Writing byte sum = b1 + b2; byte + byte is promoted to int, so compilation fails with possible lossy conversion from int to byte even when the sum is only 127.
Try it yourself
Change, predict, then run
In a browser editor, set int totalSeconds = 7325; and print it as 2:2:5 using only / and %, then print the same value as a fraction of an hour. Compare (double) totalSeconds / 3600 with (double) (totalSeconds / 3600) and explain the difference.
Open the Java workspaceCheck your understanding
Given int a = 9; int b = 4; double r = a / b * 1.0; what is r?
- 2.25, because the declared type double makes the division floating point
- 2.0, because a / b runs first with two int operands and yields 2, which then becomes 2.0
- 2.25, because the literal 1.0 promotes every operand in the expression to double
- 2.0, because assigning to a double truncates the fractional part
Show answer
Each operator sees only its own operands. a / b has two int operands, so it is integer division and gives 2; multiplying by 1.0 then widens that 2 to 2.0, but the fraction was already gone. The tempting option that credits the literal 1.0 is wrong because promotion works outward from each operator, so a double appearing later in the expression cannot reach back and change an earlier subexpression, and the declared type of r is applied only when the finished value is stored.