Type Casting & Floating-Point Precision
How to force decimal division, why 0.1 + 0.2 ≠ 0.3, and how printf brings order to floating-point chaos
Part 1 — The Truncation Problem
Java divides two int values using integer division: it computes the quotient and throws away the remainder.
int total = 397;
int count = 5;
double average = total / count; // 79 stored as 79.0 — WRONG!
Step by step:
total / count— both areint→ integer division →79(remainder 2 is discarded)79is stored inaverageas79.0
The double declaration did not help. The truncation happened before the assignment.
This is the per-operation rule: Java evaluates each operation in isolation based on the types present at that moment. The fact that the result will be stored in a double is irrelevant — the division already happened with two ints.
Part 2 — The cast Operator
A cast explicitly converts a value from one type to another. The syntax: (targetType) value.
double average = (double) total / count;
What happens:
(double) total— convertstotalfromint 397todouble 397.0397.0 / count— double divided by int → Java promotescountto5.0397.0 / 5.0 = 79.4
The cast attaches to the value immediately to its right. It does not apply to the whole expression.
Cast placement is critical
int total = 397, count = 5;
double a = (double) total / count; // 79.4 ← CORRECT
double b = total / (double) count; // 79.4 ← also correct
double c = (double) (total / count); // 79.0 ← WRONG! divides first, then casts
double d = total / count; // 79.0 ← WRONG! no cast at all
Line C is the common trap: you remembered to cast, but put the parentheses around the whole division. The integer division 397 / 5 = 79 happens first, then (double) 79 = 79.0. The truncation already happened before the cast got there.
Rule: Cast one of the operands, before the division. Either operand works:
(double) total / count // cast the numerator
total / (double) count // cast the denominator
What about other casts?
You can cast between any numeric types:
double price = 9.99;
int truncated = (int) price; // 9 — decimal part discarded, no rounding
long bigNumber = 9876543210L;
int narrowed = (int) bigNumber; // may lose data if value doesn't fit
// char ↔ int (each char has a numeric code)
char letter = 'A';
int code = (int) letter; // 65
char back = (char) 66; // 'B'
For CSCD 210, you primarily need (double) to force decimal division. The others are here for completeness.
Part 3 — Type Promotion Rules
When two different numeric types appear in an expression, Java automatically promotes the smaller type to the larger before computing.
Promotion hierarchy (smaller → larger): byte → short → int → long → float → double
Promotion is per-operation, not per-expression:
System.out.println(10 / 4); // 2 — int/int, no promotion
System.out.println(10 / 4 + 3.0); // 5.0 — (2) + 3.0: int 2 promoted to 2.0
System.out.println(10.0 / 4 + 3); // 5.5 — (2.5) + 3: int 3 promoted to 3.0
System.out.println((double) 10 / 4); // 2.5 — 10.0/4: 4 promoted to 4.0
Line 2 is the key: 10 / 4 evaluates to 2 (integer division, truncated) before 3.0 is ever seen. The 3.0 cannot undo the truncation that already happened. To get 5.5, you need 10.0 / 4 + 3.0 or (double) 10 / 4 + 3.0.
System.out.println(7 / 2 * 1.0) print?7 / 2 first — both ints → 3 (truncated). Then 3 * 1.0 — int × double → 3.0. The double arrived too late to prevent the truncation. To get 3.5, cast first: (double) 7 / 2 * 1.0.
Part 4 — Floating-Point Representation
double stores numbers in binary (base 2). Most decimal fractions — including 0.1 and 0.2 — cannot be represented exactly in binary, the same way 1/3 cannot be written exactly in decimal.
System.out.println(0.1 + 0.2); // 0.30000000000000004 — not 0.3!
System.out.println(0.1 + 0.2 == 0.3); // false
This is not a Java bug. It happens in every language that uses the IEEE 754 standard for floating-point: C, Python, JavaScript, all of them.
The error is tiny — about 5 × 10⁻¹⁷ — but == is exact. Even a difference of 0.00000000000000004 causes == to return false.
What this means for your code
Never compare doubles with ==:
double result = 0.1 + 0.2;
// WRONG: almost always false due to tiny rounding error
if (result == 0.3) { ... }
// CORRECT: check if they're "close enough"
if (Math.abs(result - 0.3) < 0.000001) { ... }
Use printf to hide the noise in output:
double average = 79.4;
System.out.println(average); // 79.4 — clean in this case
System.out.println(0.1 + 0.2); // 0.30000000000000004 — ugly
System.out.printf("%.2f%n", 0.1+0.2); // 0.30 — printf rounds for display
printf rounds the value for display purposes only. The underlying double still has the imprecision — it just doesn’t show.
Part 5 — printf for Controlled Precision
System.out.printf(formatString, values...) formats output with exact control over decimal places, column widths, and alignment.
Format specifiers
| Specifier | Type | Example | Output |
|---|---|---|---|
%.2f |
double |
printf("%.2f", 79.4) |
79.40 |
%.0f |
double |
printf("%.0f", 15.0) |
15 |
%d |
int |
printf("%d", 397) |
397 |
%s |
String |
printf("%s", "Alice") |
Alice |
%n |
— | newline | (platform newline) |
%.2f means: print a floating-point number (f) rounded to 2 decimal places (.2). Java pads trailing zeros: 79.4 becomes 79.40.
Multiple values in one call
double average = 79.4;
int passingCount = 4;
System.out.printf("Average: %.2f — %d passing%n", average, passingCount);
// Average: 79.40 — 4 passing
Values are substituted left-to-right: first %.2f gets average, then %d gets passingCount.
%n vs \n
Use %n inside printf instead of \n. %n outputs the platform-correct line separator (important on Windows), while \n hardcodes Unix-style endings. Both work on macOS/Linux, but %n is the correct choice in printf.
Part 6 — Lab 5 CP3 Pattern
int total = 397; // accumulated with += in CP1
int count = 5;
// Cast before dividing — not after
double average = (double) total / count; // 397.0 / 5 = 79.4
// printf formats to exactly 2 decimal places
System.out.printf("Class average: %.2f%n", average); // Class average: 79.40
Without (double): 397 / 5 = 79 → average stored as 79.0 → printf shows 79.00. The test checks for 79.40 — if you see 79.00, the cast is missing or in the wrong place.
The floating-point imprecision note from CP3:
// This is NOT the class average — just demonstrating the precision issue
double tenth = 0.1 + 0.2;
System.out.println(tenth); // 0.30000000000000004
System.out.printf("%.2f%n", tenth); // 0.30 — printf rounds for display
What You Learned
(double)cast before a division forces decimal arithmetic:(double) total / 5gives79.4, not79- Cast placement matters:
(double) (total / count)casts after truncation — wrong order, wrong answer - Type promotion is per-operation:
10 / 4 + 3.0 = 5.0because10/4truncates before3.0is seen 0.1 + 0.2is not exactly0.3in any language — useMath.abs()for equality,printffor displayprintf("%.2f", value)rounds to 2 decimal places and pads trailing zeros:79.4→79.40
What Comes Next
Next: loops — instead of writing total += s1; total += s2; ... five times, you write total += score once inside a loop and let the loop handle the repetition. The accumulation pattern from lesson-2c runs exactly the same way inside a loop.