First Steps
Numbers & Arithmetic
Java arithmetic works on fixed-width, typed values, so the operands' types decide the result, not the math you intend. Integer division truncates toward zero, % takes the dividend's sign, and mixed types promote to the wider one. Casts and += convert on purpose, int overflow wraps silently, and double is approximate. Also covers update operators, precedence and the Math class, with every trap shown as real output.
Suggest an editNumbers & Arithmetic — When the Type Decides the Answer
Java's arithmetic looks like the maths you already know: +, -, *, /. But it obeys one rule that the maths classroom never mentioned: the result is shaped by the operands' types, not by the answer you have in mind.
An operand is a value that an operator works on: in 7 / 2, the operands are 7 and 2.
- Divide two integers, and the fraction vanishes.
- Push an
intpast its range, and it silently wraps around to a negative number. - Mix an
intwith adouble, and the whole expression becomes adouble.
The same / means different things depending on what sits to its left and right. This chapter is about reading those types, so that the numbers come out right.
💡 The core idea.
- Java arithmetic is shaped by the operands' types, not the answer you have in mind.
- The same
/can truncate, promote, or silently overflow depending on its operands. - Reading those types is how you make the numbers come out right.
This lesson builds directly on the primitive types: the sizes and ranges you met there decide each result here. Every output below was produced by compiling and running the code on Java 25.
You'll be able to: predict the result of / and % on whole numbers, negatives included; write a division that keeps its fraction, by converting an operand before the division happens; predict when int arithmetic overflows, and fix it with long or Math.multiplyExact; update a variable with += and ++, and predict the value that a++ hands back; explain why 0.1 + 0.2 doesn't print 0.3.
📘 How to read the Intuition boxes. Each one is built in three moves:
- The mechanism — what the compiler and the JVM do.
- A concrete bite — a specific, runnable failure (often a real compiler error), shown so the trap is visible.
- The earned rule — the decision heuristic, now justified rather than asserted, plus its cost.
Table of contents
- The arithmetic operators
- Integer division truncates
- Mixing numeric types, and casting
- Silent integer overflow
- Decimals are approximate
- Precedence and the
Mathclass - Mental-model summary
- Gotcha checklist
- Check yourself
- Sources
1. The arithmetic operators
Five operators do the everyday arithmetic:
+adds,-subtracts, and*multiplies./divides; it gets its own section, coming up next.%gives the remainder: what's left over after a division.
Output:
9
5
14
1Analysis. 7 + 2 = 9, 7 - 2 = 5, and 7 * 2 = 14. 7 % 2 = 1, because 7 is 3 * 2 + 1, leaving a remainder of 1. % is how you test divisibility (a remainder of 0 when dividing by 2 means "even"). It's also how you fold a value into a range.
Intuition.
Mechanism. % returns the remainder, and in Java, its sign follows the left operand — the dividend (the number being divided) — not the right one [1].
Concrete bite. That means it isn't the mathematical "modulo," which is never negative for a positive divisor:
Output:
-1
1-7 % 2 is -1, not 1, because the result took the sign of -7. 7 % -2 is 1, taking the sign of 7. The magnitude is the remainder, but the sign comes from the dividend. When you need the never-negative version, Math.floorMod gives it to you; its result takes the divisor's sign [2]:
Output:
-1
1
-3The last line is a sneak preview of §2: -7 / 2 is -3, not -4.
💡 Earned rule. Use % for remainders and divisibility. When you need an always-non-negative "wrap" of a possibly-negative number, use Math.floorMod, which returns a non-negative result for a positive divisor. The cost of assuming % is mathematical modulo is an off-by-a-sign bug — one that appears exactly when the input goes negative.
Updating a variable: +=, ++, --
Programs often change a variable by a step: adding points to a score, or counting one more item. score = score + 5; reads the current value, adds 5, and stores the result back in score. Java has shorter forms for this:
score += 5;means the same asscore = score + 5;. There are also-=,*=,/=and%=.score++;adds 1, andscore--;subtracts 1.
Output:
15
20
21
20
40Non-example: using a++ as a value. On a line of its own, a++ simply adds 1. Inside a larger expression, though, it also hands back a value — and that value is the old one [3]. Written in front, ++a adds 1 first, and hands back the new value:
Output:
6
5
6
6Both a and c ended up at 6; only the values handed to b and d differed. Keep ++ and -- on lines of their own, and the difference never matters.
2. Integer division truncates
/ between two integers performs integer division: it gives you the whole-number quotient, and throws away the remainder.
Output:
3
0
0Analysis. 7 / 2 is 3 (not 3.5), 1 / 2 is 0, and 9 / 10 is 0. The fraction isn't rounded; it's truncated — dropped toward zero, so -7 / 2 is -3 [4]. Both operands are int, so the result is an int, and an int has nowhere to keep a fractional part.
Intuition.
Mechanism. When both operands are integer types, / computes the integer quotient. The result has that same integer type, so a fractional part simply can't exist.
Concrete bite. Here's the classic "average" bug: a double result that's produced one step too late. (The first line declares two int variables in one statement, separated by a comma.)
Output:
3.0average is a double, but the right-hand side, total / count, is int / int. It's evaluated first, as 3, and only then widened to 3.0. The division has already happened with integers, and widening the answer afterwards can't recover the lost .5. So the "average" of 7 and 2 prints 3.0.
💡 Earned rule. Integer / truncates. To get a real quotient, make sure at least one operand is floating-point before the division runs.
The cost of forgetting is a silent wrong answer, rather than an error. 3.0 looks perfectly reasonable, which is exactly what makes this Java's most common arithmetic bug. The fix is the next section's job.
Dividing an integer by zero is different, because it isn't silent. 7 / 0 and 7 % 0 throw an ArithmeticException (/ by zero) at run time [4], as What Java is showed.
3. Mixing numeric types, and casting
When operands have different numeric types, Java promotes the narrower one to the wider type before computing, so int / double becomes double / double [5]. To force a type yourself, use a cast: write the type in parentheses, such as (double), in front of a value.
Output:
3.5
3.5
3Analysis.
7 / 2.0: the2.0is adouble, so7gets promoted to7.0, and the division is7.0 / 2.0 = 3.5.(double) total / count: the cast turnstotalinto7.0first, because(double)binds tototalalone. So it's7.0 / 2 = 3.5.(int) 3.9is a narrowing cast, to a type with a smaller range. It chops the fraction off toward zero, giving3, not4.
Intuition.
Mechanism. A cast (double) x produces a value of the target type for that one spot, and automatic promotion does the same thing when types mix. Precedence matters here: a cast binds tightly, to the operand right after it. So (double) total / count casts total, then divides.
Concrete bite. Cast the result instead of an operand, and the bug survives:
Output:
3.0The parentheses force 7 / 2 to happen first (integer division → 3), and only then cast 3 to 3.0. You converted the answer after the fraction was already gone. Contrast that with (double) 7 / 2, which casts 7 first and gives 3.5.
💡 Earned rule. To divide as real numbers, get a floating-point operand into the expression before the division: (double) total / count, not (double) (total / count). The cost of casting is that narrowing casts ((int) 3.9) silently truncate. They throw information away on purpose — you're telling the compiler, "I accept the loss." That same silent loss, when it happens uninvited, is the next trap.
Small types are promoted to int
Promotion has a second rule: arithmetic never runs on byte, short or char. Each operand is promoted to at least int first [5]. So byte + byte is an int, which doesn't fit back into a byte:
Compiler error:
Main.java:5: error: incompatible types: possible lossy conversion from int to byte
byte sum = a + b;
^
1 errora + b is 30, which fits in a byte. But a and b are variables, not constants, so the compiler checks the type of a + b — and that's int. The same promotion makes a char act as its character code: 'A' + 1 is the int 66. Cast it back with (char) ('A' + 1), and you get B.
Output:
66
B
BA cast to a smaller type can change the value
A narrowing cast between whole-number types keeps only the bits that fit [6], so a value outside the target range comes out as a different number. A double that's too big for an int, meanwhile, stops at the int limit:
Output:
3
-3
-56
2147483647(int) -3.9 is -3: toward zero, not down. (byte) 200 is -56 — a different number entirely, with no warning. 3.0e10 is 3 × 10¹⁰ written in scientific notation; cast to an int, it stops at 2147483647.
Non-example: += hides a cast. b = b + 1; on a byte doesn't compile, for the reason above. But b += 1; does, because a compound assignment casts its result back to the variable's type [7]. And that hidden cast wraps, just like any other:
Output:
11
-126120 + 10 is 130, which is past a byte's 127, so the hidden cast turned it into -126. The compiler said nothing, because += had promised the cast.
4. Silent integer overflow
An int is 32 bits, with a range of −2147483648 to 2147483647. When arithmetic produces a result outside that range, Java does not grow the type to fit. Instead, it silently wraps around to the opposite end. There's no error at all: the integer operators don't signal overflow in any way [8].
Output:
2147483647
-2147483648Analysis. Integer.MAX_VALUE is the largest int, 2147483647. Add 1, and instead of 2147483648, you get -2147483648 — the smallest int. The value rolled over the top of the range straight to the bottom, like a car odometer passing its maximum. There's no exception and no warning, just a negative number where a larger positive one was expected.
Intuition.
Mechanism. int + int is always an int, and the result keeps only the low 32 bits. When the true answer needs a 33rd bit, that bit gets discarded — and because of how signed integers are encoded, discarding it flips the sign. The type never widens itself to fit.
Concrete bite. Overflow hides in ordinary code, such as the product of two modest numbers:
Output:
1410065408100000 * 100000 is ten billion, far beyond an int, so it wraps to a meaningless 1410065408. Nothing looked dangerous, yet the type quietly betrayed the arithmetic.
Non-example: a long box, too late. Storing the product in a long doesn't help. The multiplication still runs as int * int, wraps, and only then widens — just like average in §2. Make one operand a long before the multiplication:
Output:
1410065408
10000000000
10000000000💡 Earned rule. When a value can pass 2147483647, use long (64 bits, up to 9223372036854775807). Make at least one operand a long, so the operation runs in 64 bits: 100000L * 100000 gives the correct 10000000000. The cost of ignoring overflow is the most dangerous kind of bug there is: a wrong number with nothing to flag it. When you need to be certain, Math.multiplyExact and Math.addExact throw instead of wrapping [2]:
Output (a thrown exception; the full stack trace follows the first line, pointing at the multiplyExact call):
Exception in thread "main" java.lang.ArithmeticException: integer overflowSwapping a silent wrong answer for a loud ArithmeticException is usually the trade you want. Exceptions covers exceptions in full.
5. Decimals are approximate
A double stores a number in binary, in 64 bits. Most decimal fractions — 0.1 among them — have no exact binary form, so a double holds the nearest value it can [9]. That tiny difference shows up in arithmetic:
Output:
0.30000000000000004
0.09999999999999998
0.3333333333333333Analysis. Neither 0.1 nor 0.2 is stored exactly, so their sum lands a hair above 0.3. 1.0 - 0.9 lands a hair below 0.1. And 1.0 / 3 has no exact form in decimal either, so println shows 16 digits of it. None of this is a bug in Java: it's simply how binary floating-point works, in every language that uses it.
Intuition.
Mechanism. double trades exactness for range and speed. Each value is the nearest member of a fixed set of binary fractions, and each operation rounds its result to that set.
Concrete bite. Unlike integer division, floating-point division by zero doesn't throw. Instead, it gives a special value: Infinity, or NaN ("not a number") for 0.0 / 0 [4]. And the program just carries on with it:
Output:
Infinity
-Infinity
NaN💡 Earned rule. Treat double results as close, not exact: expect 0.30000000000000004, and never rely on a double landing on an exact decimal. For money, count in whole cents with long, or use java.math.BigDecimal, which stores decimal digits exactly [10]. The cost is ceremony: you add two BigDecimal values with a method call, not with +.
6. Precedence and the Math class
When several operators meet in one expression, Java follows precedence rules. *, / and % bind tighter than + and -, and parentheses override that order. Operators at the same level run from left to right, so 10 - 4 - 3 is (10 - 4) - 3, which is 3. For arithmetic beyond the basic operators, the Math class supplies sqrt, pow, abs, max, min, and more [2].
Output:
14
20
10
12.0
1024.0Analysis.
2 + 3 * 4is2 + 12 = 14, because*binds tighter than+. Parentheses force5 * 4 = 20instead.Math.maxreturns the larger value, andMath.sqrt(144.0)is12.0.Math.pow(2, 10)is1024.0. Notice the.0:Math.powalways returns adouble, so even a whole-number power comes back as a decimal.
Left-to-right order matters for - and /, where the order changes the answer:
Output:
3
1
2
-68 / 4 / 2 is (8 / 4) / 2 = 1, not 8 / (4 / 2) = 4. And 2 * 3 % 4 is 6 % 4 = 2, because * and % share a level, so they run left to right too.
Intuition.
Mechanism. Precedence is fixed grammar that the compiler applies before anything runs. Math.pow and Math.sqrt are library methods that take and return double.
Concrete bite. Assuming that Math.pow produces an integer bites you the moment you store its result in an int:
Compiler error:
Main.java:3: error: incompatible types: possible lossy conversion from double to int
int kib = Math.pow(2, 10);
^
1 errorMath.pow returns 1024.0, a double, and a double won't fit into an int without losing its fractional part, so the compiler refuses. The fix is a deliberate cast: int kib = (int) Math.pow(2, 10);, which gives 1024.
💡 Earned rule. Let precedence and Math do the arithmetic, but remember that Math.pow and Math.sqrt return double. Assign their results to a double, or cast deliberately when you want an int, accepting the truncation. And reach for parentheses whenever the grouping isn't obvious at a glance: they cost nothing, and they prevent precedence surprises.
7. Mental-model summary
| Principle | Consequence |
|---|---|
| The operands' types decide an operator's behaviour | The same / means integer or floating-point division depending on its operands |
int / int truncates toward zero; % takes the dividend's sign |
7 / 2 is 3, -7 / 2 is -3, -7 % 2 is -1; Math.floorMod for a non-negative wrap |
a++ hands back the old value, ++a the new one |
Keep ++ and -- on lines of their own |
Mixed types promote to the wider; byte, short and char promote to int |
Put a floating-point operand in before dividing: (double) total / count; byte + byte is an int |
Narrowing casts, and the hidden cast in +=, silently drop information |
(int) 3.9 is 3; (byte) 200 is -56; a byte of 120 after += 10 is -126 |
int arithmetic wraps past its range with no error |
Integer.MAX_VALUE + 1 is negative; make an operand long before the operation, or use Math.*Exact to throw |
double is approximate; floating-point / 0 gives Infinity or NaN |
0.1 + 0.2 prints 0.30000000000000004; count money in long cents or BigDecimal |
* / % bind tighter than + -, and each level runs left to right; Math.pow/sqrt return double |
Parenthesise for clarity; store Math.pow results in a double |
8. Gotcha checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| A division gives a whole number when you expected a fraction | both operands are integers | make one floating-point before dividing: (double) a / b, or a / 2.0 |
double x = a / b; prints a .0 value |
the division ran as int / int, then widened |
cast an operand, not the result |
| A sum or product is suddenly negative or absurd | int overflow |
make an operand long ((long) a * b, 100000L), or use Math.addExact / Math.multiplyExact |
long x = a * b; is still wrong |
a * b ran as int * int and wrapped before the widening |
cast an operand: (long) a * b |
ArithmeticException: / by zero |
an integer / or % by 0 |
check the divisor before dividing |
Infinity or NaN in the output |
a floating-point division by zero | check the divisor before dividing |
0.30000000000000004 where you expected 0.3 |
double is binary and approximate |
expect it; for money use long cents or BigDecimal |
possible lossy conversion from int to byte on byte c = a + b |
byte operands are promoted to int |
declare c as int, or cast: (byte) (a + b) |
A byte or short turns negative after += |
the hidden cast in += wrapped a value past the range |
use int or a wider type |
possible lossy conversion from double to int |
a double (often a Math.pow/sqrt result) went into an int |
assign to a double, or cast explicitly |
% of a negative number has the "wrong" sign |
the remainder follows the dividend | use Math.floorMod for a non-negative result |
b = a++ gave b the old value |
postfix ++ hands back the value before the increment |
put a++; on its own line, or use ++a |
| An expression groups unexpectedly | precedence (* before +), or left-to-right order (8 / 4 / 2) |
add parentheses to force your order |
✅ Check yourself
One check per objective. Answer before you open anything.
Why does System.out.println(0.1 + 0.2); print 0.30000000000000004? And what does 7.0 / 0 print?
A double stores binary fractions, and 0.1 and 0.2 have no exact binary form [9]. Each one is stored as the nearest value, and the sum of those two nearest values is a hair above 0.3.
7.0 / 0 prints Infinity. Floating-point division by zero gives a special value; only integer division by zero throws.
The 🧪 box below: what do the four lines print?
5 / 2 prints 2: both operands are int, so the division truncates. 5 / 2.0 prints 2.5: 2.0 is a double, so 5 is promoted. (double) 5 / 2 prints 2.5: the cast makes 5 a double before the division.
(double) (5 / 2) prints 2.0: the parentheses run 5 / 2 first, as integers, giving 2, and only then cast the result.
📚 Sources
- The Java Language Specification, Java SE 25, §15.17.3 "Remainder Operator
%" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.17.3 - Java SE 25 API,
java.lang.Math(floorMod,addExact,multiplyExact,pow) — https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/Math.html - The Java Language Specification, Java SE 25, §15.14.2 "Postfix Increment Operator
++" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.14.2 - The Java Language Specification, Java SE 25, §15.17.2 "Division Operator
/" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.17.2 - The Java Language Specification, Java SE 25, §5.6 "Numeric Contexts" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html#jls-5.6
- The Java Language Specification, Java SE 25, §5.1.3 "Narrowing Primitive Conversion" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-5.html#jls-5.1.3
- The Java Language Specification, Java SE 25, §15.26.2 "Compound Assignment Operators" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html#jls-15.26.2
- The Java Language Specification, Java SE 25, §4.2.2 "Integer Operations" — https://docs.oracle.com/javase/specs/jls/se25/html/jls-4.html#jls-4.2.2
- Java SE 25 API,
java.math.BigDecimal, constructorBigDecimal(double)("0.1 cannot be represented exactly as adouble") — https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/math/BigDecimal.html#%3Cinit%3E(double) - Java SE 25 API,
java.math.BigDecimal— https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/math/BigDecimal.html
🧪 Predict, then check. Predict the exact output of each line before running it. System.out.println(5 / 2); · System.out.println(5 / 2.0); · System.out.println((double) 5 / 2); · System.out.println((double) (5 / 2));. Three of the four differ.
Then explain, in terms of when the division happens and what types its operands have, why two of them print 2.5, one prints 2, and one prints 2.0.
Your Turn
Before you move on, check your understanding with the coach — explain the idea, apply it, weigh the trade-offs, then defend your reasoning.