Floating-Point, Strings, and the Three Kinds of Error

The double type and the integer-division trap it is meant to solve, why 0.1 added ten times is not 1.0, what the plus operator does to strings, the order of operations, and the three kinds of error every programmer learns to tell apart. Follows Think Java 2e, Chapter 2 (Variables and Operators), Sections 2.6-2.10, pp. 23-29, cross-referenced against The Java Tutorials — Primitive Data Types.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Floating-Point, Strings, and the Three Kinds of Error

Title

Think Java 2e · Chapter 2 · Variables and Operators

Sections 2.6-2.10 · pp. 23-29

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 2 (Variables and Operators), Sections 2.6-2.10, pp. 23-29. Everything on these slides can be checked against those pages.

1. Declare and use double variables, and say when Java performs floating-point rather than integer division.

2. Explain why double y = 1 / 3; gives 0.0, and fix it.

3. Say what a rounding error is and name a case where you should use integers instead of doubles.

4. Predict the result of the + operator when strings and numbers are mixed.

5. Apply Java's order of operations, and use parentheses deliberately rather than defensively.

6. Classify any error as compile-time, run-time or logic, and say which of the three the compiler can help with.

3. Retrieve before you read

Warm-up

One thing from the previous lesson, because this one exists to fix it.

Discussion prompt

From memory: what does System.out.println(59 / 60); display, and why? Then guess — what would you have to change to get 0.98333 instead?

Hint: The answer to the first part is a single character long.

Answer:

It displays 0. Both operands are integers, so Java performs integer division, which rounds toward zero and discards the remainder.

To get the fraction you need a different type — one that can hold values with decimal places. That type is double, and Section 2.6 is about it. Your guess was probably 'use decimals somewhere', which is right; the interesting part is exactly where.

4. Where this lesson is going

Concept

Two new capabilities and one new way of thinking. The capabilities are decimal numbers and joining strings together. The way of thinking is the three-way classification of errors, which you will use for the rest of your programming life.

Figure (svg): Three boxes naming compile-time errors, run-time errors and logic errors, with when each one appears

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 2 (Variables and Operators), Sections 2.6-2.10, pp. 23-29 — Sections 2.6-2.10, printed pages 23-29.

5. Floating-point numbers

Section

Section 2.6

6. double holds values with decimal places

Concept

A more general solution to the integer-division problem is to use floating-point numbers, which represent values with decimal places. In Java the default floating-point type is called double, short for double-precision.

double pi;
pi = 3.14159;

double minute = 59.0;
System.out.print("Fraction of the hour that has passed: ");
System.out.println(minute / 60.0);
expressionoperand typeskind of divisionresult
59 / 60int, intinteger0
59.0 / 60.0double, doublefloating-point0.9833333333333333
59.0 / 60double, intfloating-point0.9833333333333333

Java performs floating-point division when one or more operands are double values. One is enough — which is both convenient and, as the next slides show, a source of confusion.

7. Where the type of a division comes from

Notation

The single most useful thing to internalise in this chapter: look at the operands, not at the variable you are assigning to.

Annotate

  • The operands decide, and nothing else does. The variable on the left of the assignment has no say at all — this is the rule beginners get wrong.
  • Only the first row loses information. Integer division discards the remainder; the other three keep it.
  • One double is enough to make the whole division floating-point. Java converts the other operand automatically.
  • So the fix for a wrong-looking division is always the same: make at least one operand a double, by writing 60.0 instead of 60 or by declaring the variable as a double.

Read this table left to right, not right to left. The question is never 'what do I want out?' but 'what did I put in?'

8. The mistake this section exists to prevent

Worked example

This looks correct, compiles cleanly, and produces the wrong answer. Work out where the information is lost.

double y = 1 / 3;
System.out.println(y);   // displays 0.0
stepwhat happensvalue
1 / 3both operands are int, so integer division0 (an int)
assignment to a doubleJava converts the int 0 to a double0.0
println(y)displays the double0.0

Read the right-hand side on its own, ignoring the variable.

Why: 1 / 3 — two integers. Java has no reason to think otherwise.

Perform integer division.

Why: The result is the int 0. The decimal part never existed; it was not computed and then rounded.

Only now look at the left-hand side.

Why: y is a double, so Java converts 0 to 0.0 — faithfully preserving a value that was already wrong.

Fix it by changing the operands.

Why: double y = 1.0 / 3.0; sets y to 0.3333333333333333, as expected.

Verify: Run both versions and expect 0.0 from the first and 0.3333333333333333 from the second.

Why: The lesson is the ordering: the right-hand side is fully evaluated BEFORE the assignment happens, so the variable's type cannot rescue an expression that has already lost its precision.

As a matter of style, always assign floating-point values to floating-point variables. The compiler will not make you, but you never know when a simple mistake will come back to haunt you.

9. What is stored in y?

Prediction

Read the right-hand side first, on its own.

double y = 5 / 2;
stepvaluetype
5 / 22int — integer division
converted on assignment2.0double

Predict first

What value does y hold?

  • 2.0
  • 2.5
  • 2
  • 3.0

Correct: 2.0

Why: Both operands are integers so Java performs integer division, giving the int 2, and only then converts that to the double 2.0 for storage. The declared type of y affects how the value is stored, never how the expression on the right is computed.

10. Java's automatic conversions, and why they are risky

Concept

Strictly speaking you are not allowed to make assignments between types. In many cases Java converts automatically anyway — and that leniency is convenient right up to the moment it hides a bug.

statementlegal?what Java does
double y = 1;yesconverts the int 1 to the double 1.0 — legal, but bad style
int x = 1.1;nocompiler error — this would lose information, so Java refuses
double y = 1 / 3;yescomputes the int 0, then converts to 0.0 — legal and almost certainly wrong
double y = 1.0 / 3.0;yesfloating-point division throughout — correct

Notice the asymmetry. Java will widen an int to a double without complaint, because nothing is lost. It refuses to narrow a double to an int, because something would be. The dangerous row is the third, where the loss happened before the conversion Java was willing to make.

11. The variable's type cannot fix the expression

Trap

The trap

The assumption. Declaring the variable as a double makes the arithmetic floating-point.

double average = 7 / 2;
System.out.println(average);   // expected 3.5
stagetypes involvedvalue
evaluate 7 / 2int, int3
assign to a doubleint converted to double3.0
displaydouble3.0 — not 3.5

The precision was gone before the assignment was even considered. Java did convert to a double, exactly as promised — it converted the wrong number.

The fix

The fix. Make at least one operand a double, so the division itself is floating-point.

double average = 7.0 / 2;
System.out.println(average);   // 3.5
stagetypes involvedvalue
evaluate 7.0 / 2double, int -> floating-point3.5
assign to a doublealready a double3.5
displaydouble3.5

One character. Writing 7.0 instead of 7 changes which division Java performs, and that is the only thing that ever changes it.

12. Which division is floating-point?

Definition probe

Look only at the operands.

Sort into buckets

Sort each expression by the kind of division Java performs.

integer division
9 / 4; hour / 60, where hour is an int
floating-point division
9.0 / 4; 9 / 4.0; 9.0 / 4.0
int
Both operands are integers, so the remainder is discarded and the result rounds toward zero.
fp
At least one operand is a double, which is enough — Java converts the other one and computes a floating-point result.

13. Fix the average

Fill the middle

Make this compute 3.5 rather than 3.0, changing as little as possible.

Fill in the blanks

double average = 7.0 / 2;

Why: Writing 7.0 makes one operand a double, which is enough to make the whole division floating-point and give 3.5. Changing the declared type of the variable would not help, because the expression on the right is evaluated before the assignment ever happens.

14. Is automatic conversion ever a good thing?

Counterexample

You have just seen it hide a bug. Argue the other side.

Discussion prompt

Java silently converts double y = 1; into double y = 1.0;. Given that this leniency causes the 1 / 3 bug, why do you think the language designers allowed it? What would writing Java be like if every conversion had to be explicit?

Hint: Think about how often you would have to write .0.

Answer:

Without it, every mixed expression would need a manual conversion — x * 2 would be illegal for a double x, and you would write x * 2.0 everywhere. The noise would be constant and most of it would be pointless.

The design rule Java actually follows is: widening conversions are automatic, narrowing ones are not. int to double loses nothing, so it is silent; double to int loses the fractional part, so it must be written out (Chapter 3 shows how). The 1 / 3 bug slips through because the loss happens in the division, before any conversion is involved — so the rule is not violated, merely unhelpful.

15. Rounding errors

Section

Section 2.7

16. Most floating-point numbers are only approximate

Concept

Some numbers, like reasonably sized integers, can be represented exactly. But repeating fractions like one third, and irrational numbers like pi, cannot. To represent these, computers round off to the nearest floating-point number — and the difference is called rounding error.

System.out.println(0.1 * 10);
System.out.println(0.1 + 0.1 + 0.1 + 0.1 + 0.1
                 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1);
statementoutput on many machines
0.1 * 101.0
0.1 added ten times0.9999999999999999
mathematicallythese are the same number

rounding error — The difference between the number we want and the floating-point number we actually get.

The problem is that 0.1 is a repeating fraction when converted into binary, so what is stored is only approximate. Adding the approximations up makes the errors accumulate.

17. Why one tenth is awkward in binary

Picture it

One third is awkward in decimal — 0.333... never terminates. One tenth is awkward in binary for exactly the same reason.

Figure (svg): Two panels comparing one third written in decimal with one tenth written in binary, both non-terminating

Nothing is broken. A number that needs infinitely many digits in a base cannot be written exactly in finitely many — and a computer has finitely many. The surprise is only that the awkward numbers in binary are different from the awkward numbers in decimal.

18. Choosing integers for money

Worked example

Think Java gives a concrete case where rounding error is not acceptable, and the fix is to change the type rather than to add more decimal places.

// potential rounding error:
double balance = 123.45;

// exact, as long as the amount fits in an int:
int balance = 12345;   // total number of cents
representationstoresexact?limit
double balance = 123.45;an approximation of 123.45noerrors accumulate over many operations
int balance = 12345;the number of centsyesabout 2 billion cents

Ask whether the quantity is really continuous.

Why: Money is not: there is a smallest unit, the cent. Nothing between 1 and 2 cents exists.

Represent it as a whole number of the smallest unit.

Why: Store cents in an int rather than pounds or dollars in a double.

Check the range.

Why: This works as long as the number of cents does not exceed the largest int, about 2 billion.

Convert only for display.

Why: Divide by 100 at the moment you print, not while you compute.

Verify: Add 0.10 to a double balance ten times and print it; then add 10 to an int balance of cents ten times and print that.

Why: The double drifts; the int does not. Think Java's summary of the stakes is blunt — inaccurate balances mean angry customers and potential lawsuits.

19. What does this display?

Prediction

One of these two lines surprises people.

System.out.println(0.1 + 0.2);
valuestored assum
0.1slightly more than one tenth—
0.2slightly more than two tenths—
0.1 + 0.2the two approximations added0.30000000000000004

Predict first

What is displayed on a typical machine?

  • 0.30000000000000004
  • 0.3
  • 0.30000000000000000
  • 0.29999999999999

Correct: 0.30000000000000004

Why: Neither 0.1 nor 0.2 can be stored exactly in binary, so their stored approximations add to something very slightly more than 0.3, and Java prints enough digits to show it. This is the same phenomenon as the ten-additions example: the errors are individually invisible and become visible when combined.

20. When floating point is the right choice

Concept

None of this means doubles are bad. For many applications the benefits far outweigh the costs — the question is whether your quantity is continuous or countable.

applicationusewhy
computer graphicsdoublepositions are continuous; a tiny error is invisible
encryptionintegersan exact value is the whole point
statistical analysisdoublemeasurements are approximate anyway
multimedia renderingdoublesmall errors are below perception
moneyintegers (cents)there is a smallest unit, and errors accumulate
counting thingsintegersthere is no such thing as 2.5 students

The rule of thumb: if you need absolute precision, use integers. If the quantity is genuinely continuous and small errors do not accumulate into something a person would notice, a double is right.

21. Comparing doubles for exact equality

Trap

The trap

The natural test. Add a tenth ten times and check whether you have arrived at one.

double total = 0.0;
// ... 0.1 added ten times ...
if (total == 1.0) {
    System.out.println("exactly one");
}
what you expectwhat is storedthe test
1.00.9999999999999999false
the message printsit does notno error, no warning

This compiles, runs, and quietly does nothing. It is a logic error — the third kind, which you will meet properly at the end of this lesson.

The fix

Two ways out. Either avoid the accumulation entirely, or compare with a tolerance instead of exactly.

// best: do not accumulate rounding error at all
int tenths = 0;
// ... 1 added ten times ...
if (tenths == 10) { ... }

// or: allow a small difference
if (Math.abs(total - 1.0) < 0.000001) { ... }
approachwhat it relies on
count in integersexactness — no rounding error is ever introduced
compare with a tolerancethe error being smaller than the tolerance you chose

You will meet if properly in Chapter 5 and Math.abs in Chapter 4. The idea is worth having now: never test two doubles for exact equality unless you can prove no rounding has occurred.

22. double or integer?

Discrimination

For each quantity, decide which representation you would choose.

Sort into buckets

Sort each quantity by the type you would use to store it.

integer
a bank balance; the number of students in a class; a price in a shop
double
the position of a character on screen in a game; a temperature reading from a sensor
int
There is a smallest indivisible unit — a cent, a person — and exactness matters because errors would accumulate or the value would be nonsensical.
dbl
The quantity is genuinely continuous, the input is already approximate, and a tiny error has no consequence anyone would notice.

23. Why store cents rather than more decimal places?

Socratic

There is an obvious-looking alternative fix. Test it.

Discussion prompt

Instead of storing money as an integer number of cents, why not just use a floating-point type with more digits of precision? Would that solve the problem, or postpone it?

Hint: Ask whether the problem is the number of digits or something else.

Answer:

It postpones it. More precision means a smaller error per operation, but one tenth is still a repeating fraction in binary at any precision — so the error is never zero, and it still accumulates.

Storing cents changes the kind of number, not its size: every value you care about becomes a whole number, and whole numbers within range are stored exactly. That is a solution rather than a mitigation, which is why financial software does it.

24. How much error, after a million operations?

Estimation

You add one cent, stored as the double 0.01, a million times to a balance. Estimate the outcome.

Predict first

Roughly what happens to the accumulated total?

  • It drifts away from the exact value by a small but growing amount
  • It stays exactly right — the errors cancel out
  • It becomes completely wrong within a few hundred additions
  • Java detects the drift and corrects it

Correct: It drifts away from the exact value by a small but growing amount

Why: Each addition introduces a tiny rounding error and they accumulate rather than cancelling, so the total drifts steadily. It does not become wildly wrong, which is precisely what makes it dangerous — the figure still looks plausible, so nothing alerts you until someone reconciles the accounts.

25. The + operator on strings

Section

Section 2.8

26. Plus means concatenation for strings

Concept

In general you cannot perform mathematical operations on strings, even if the strings look like numbers. The + operator is the exception — but it does not add. For strings it performs concatenation, which means joining end to end.

// illegal:
// "Hello" - 1
// "World" / 123
// "Hello" * "World"

// legal:
String greeting = "Hello, " + "World!";      // "Hello, World!"
String name = "Ada";
String personal = "Hello, " + name;           // "Hello, Ada"
expressionlegal?result
"Hello" - 1nosubtraction is not defined for strings
"World" / 123nodivision is not defined for strings
"Hello, " + "World!"yes"Hello, World!"
"Hello, " + nameyes"Hello, Ada"

concatenation — Joining two strings end to end. In Java this is what the + operator does when its operands are strings.

This is the first operator you have met that means two different things depending on the types of its operands. That flexibility is useful and it is also the source of the next slide's surprise.

27. Same operator, two jobs

Picture it

+ looks at what it has been given and behaves accordingly. Nothing else in this chapter does that.

Figure (svg): A rule card showing that plus adds when both operands are numbers and concatenates when either is a string

The second line is the important one. If either operand is a string, the result is a string — and whatever the other operand was gets converted into text to make that possible.

28. The classic mixed-type surprise

Worked example

Since addition is defined for both numbers and strings, Java performs automatic conversions you may not expect. Both of these lines are legal and their outputs differ.

System.out.println(1 + 2 + "Hello");   // 3Hello
System.out.println("Hello" + 1 + 2);   // Hello12
expressionfirst stepsecond stepoutput
1 + 2 + "Hello"1 + 2 is 3 (both numbers)3 + "Hello" is "3Hello"3Hello
"Hello" + 1 + 2"Hello" + 1 is "Hello1""Hello1" + 2 is "Hello12"Hello12

Java executes these operations from left to right.

Why: Both expressions have two + operators of equal precedence, so the leftmost happens first.

In the first line, the leftmost + has two numbers.

Why: So it adds: 1 + 2 gives 3. Only then does a string appear.

In the second line, the leftmost + has a string on its left.

Why: So it concatenates: "Hello" + 1 gives "Hello1", and the 1 has become text.

Once a string enters, everything after it is concatenation.

Why: The result of a concatenation is a string, so the next + has a string operand too.

Verify: Run both lines and expect 3Hello and Hello12.

Why: If you predicted Hello3 for the second, you assumed Java would add the numbers first wherever they appear — but position matters, because evaluation is strictly left to right.

The practical consequence: when you want numbers added inside a concatenation, put them in parentheses.

29. What does this display?

Prediction

Evaluate strictly left to right.

System.out.println("Sum: " + 3 + 4);
stepoperandsresult
1"Sum: " and 3"Sum: 3"
2"Sum: 3" and 4"Sum: 34"

Predict first

What appears on the screen?

  • Sum: 34
  • Sum: 7
  • Sum: 3 4
  • a compile error

Correct: Sum: 34

Why: The leftmost + has a string on its left, so it concatenates rather than adds, turning 3 into text. The result is a string, so the second + concatenates too. To get Sum: 7 you would write "Sum: " + (3 + 4), forcing the addition to happen first.

30. Forcing the addition you meant

Concept

Once you know the rule, the fix is mechanical: parentheses make the addition happen before any string gets involved.

expressionoutputwhy
"Total: " + 1 + 2Total: 12concatenation all the way — the numbers become text
"Total: " + (1 + 2)Total: 3the parentheses add first, then the result is concatenated
1 + 2 + " items"3 itemsthe numbers are added before the string appears
"" + 1 + 212an empty string is enough to turn everything into concatenation

The third row is worth noticing: sometimes the ordering already does what you want, and no parentheses are needed. But writing them anyway makes the intent obvious to a reader, which is a good enough reason.

31. Adding two numbers inside a message

Trap

The trap

The natural attempt. Label a sum by putting the text first.

int a = 20, b = 22;
System.out.println("The answer is " + a + b);
stepexpressionresult
leftmost +"The answer is " + a"The answer is 20"
next +"The answer is 20" + b"The answer is 2022"
output—The answer is 2022

No error. The numbers were converted to text and joined, exactly as the rule says — the result is just not what was wanted.

The fix

Parenthesise the arithmetic so it happens before any string is involved.

int a = 20, b = 22;
System.out.println("The answer is " + (a + b));
stepexpressionresult
parentheses firsta + b42 (a number)
then the +"The answer is " + 42"The answer is 42"
output—The answer is 42

This is the most common use of parentheses you will write in your first month of Java, and forgetting them produces the most recognisable beginner bug there is: two numbers stuck together.

32. Order these by output length

Ranking

All four are legal. Work out each result, then order them shortest output first.

Put in order

  1. 1 + 2 + "x" gives 3x
  2. "x" + (1 + 2) gives x3
  3. "x" + 1 + 2 gives x12
  4. "x" + 1 + 2 + 3 gives x123

Why: 3x and x3 are both two characters, then x12 is three and x123 is four. The pairs to compare are the first two: putting the string on the right lets the numbers add, while putting it on the left turns every following number into text. Parentheses restore the addition wherever you need it.

33. Match each expression to its result

Matching

Four expressions using the + operator.

Match the pairs

  • a. "a" + "b"
  • b. 1 + 2
  • c. "1" + 2
  • d. 1 + 2 + ""
  • r1. "ab"
  • r2. 3 (a number)
  • r3. "12"
  • r4. "3"

Why: Two strings concatenate; two numbers add; a string with a number concatenates and converts the number to text. The last one is the subtle case — the numbers add first because the empty string is rightmost, and then the result 3 is converted to the string "3".

34. State the rule for + in one sentence

Explain it to yourself

Precisely enough to predict any of the cases you have seen.

Discussion prompt

Write a single sentence describing what the + operator does, covering both numbers and strings, and including the order in which multiple + operators are applied.

Hint: Two clauses: what decides the behaviour, and what order they happen in.

Answer:

Something like: + adds when both operands are numbers and concatenates when either is a string, converting the other operand to text; when several + operators appear, they are applied from left to right.

The clause people leave out is the last one, and it is the one that explains why 1 + 2 + "x" and "x" + 1 + 2 differ. Without left-to-right, both expressions would be ambiguous.

35. The order of operations

Section

Section 2.8, continued

36. Precedence, then left to right

Concept

When more than one operator appears in an expression, they are evaluated according to the order of operations. Generally Java evaluates from left to right, but for numeric operators it follows mathematical conventions.

Figure (svg): Three boxes showing the order of operations: parentheses first, then multiplication and division, then addition and subtraction

Oracle publishes a complete table of operator precedence. You do not need to memorise it — but you should internalise these three rules, because they cover nearly everything you will write.

37. Why left-to-right matters for equal precedence

Notation

Think Java makes a specific point here that is easy to skim past, and it is the reason minute * 100 / 60 works at all.

Annotate

  • Both operators have the same precedence, so precedence alone does not decide the order — the left-to-right rule does.
  • Left to right multiplies first, which scales the value up before any information can be lost to integer division.
  • Right to left would divide first, and 100 / 60 is 1 by integer division — destroying the precision and giving 59.
  • So the left-to-right rule is not a technicality here. It is the difference between a correct percentage and a meaningless number, and it only shows up because integer division loses information.
  • You can add parentheses to make it obvious: (minute * 100) / 60 computes exactly the same thing and says so out loud.

Parentheses that do not change the result are still worth writing when they make the intent clear to a reader. That is a real use of them, not a crutch.

38. Evaluating a mixed expression by hand

Worked example

Apply the rules in order — parentheses, then precedence, then left to right — writing each intermediate form down.

int a = 2, b = 3, c = 4;
System.out.println(a + b * c - (a + b));
stepexpressionrule applied
0a + b * c - (a + b)as written
12 + 3 * 4 - (2 + 3)substitute each variable's value
22 + 3 * 4 - 5parentheses first
32 + 12 - 5multiplication before addition
414 - 5left to right: the addition
59then the subtraction

Substitute every variable with its current value first.

Why: This is the same first step as any expression evaluation — it removes all the names.

Evaluate anything in parentheses.

Why: (2 + 3) becomes 5. Parentheses always go first, regardless of what is inside them.

Apply the higher-precedence operators.

Why: 3 * 4 becomes 12, before any addition or subtraction happens.

Work left to right through what remains.

Why: 2 + 12 is 14, then 14 - 5 is 9. Addition and subtraction share a precedence level, so order is decided by position.

Verify: Run it and expect 9.

Why: If you got 3, you subtracted before adding; if you got 15, you added before multiplying. Writing each intermediate line out is what makes such a slip visible rather than mysterious.

39. What is the value?

Prediction

Precedence, then left to right.

System.out.println(10 - 4 - 3);
stepexpression
as written10 - 4 - 3
leftmost first6 - 3
result3

Predict first

What is displayed?

  • 3
  • 9
  • -3
  • 11

Correct: 3

Why: Both operators are subtraction, so they have equal precedence and are applied left to right: 10 - 4 is 6, then 6 - 3 is 3. Applying them right to left would give 10 - 1 = 9, which is why the left-to-right rule has to be stated rather than assumed.

40. Parentheses are free; confusion is not

Concept

Think Java's advice is pragmatic: any time you want to override the order of operations, or you are not sure what it is, use parentheses.

writtenvalueclear to a reader?
minute * 100 / 6098correct, but requires knowing the left-to-right rule
(minute * 100) / 6098yes — the grouping is stated
"Total: " + a + bconcatenation twicemisleading — it looks like addition
"Total: " + (a + b)the sum, labelledyes

The second and fourth rows are the pattern to adopt. Over time you should internalise these details of the language — but a parenthesis costs nothing and a misread expression can cost an afternoon.

41. Four expressions, four confident wrong answers

Error analysis

A student's predictions, each with the reasoning that produced it. Find the rule being misapplied in each case.

Annotate

  • 1 + 2 * 3 predicted 9 — evaluated strictly left to right, ignoring precedence. Multiplication happens first, giving 1 + 6 = 7.
  • 2 + 4 / 2 predicted 3 — same mistake: 2 + 4 was done first. Division has higher precedence, so it is 2 + 2 = 4.
  • (1 + 2) * 3 predicted 7 — precedence was applied even though parentheses were present. Parentheses beat precedence, always: 3 * 3 = 9.
  • "n=" + 1 + 2 predicted n=3 — the numbers were added first because they look like they belong together. But the leftmost + has a string operand, so it concatenates, and everything after becomes text.
  • The pattern: three of the four come from applying one rule and forgetting the others. The order is always the same — parentheses, then precedence, then left to right — and all three must be checked in that order.

When an expression surprises you, do not stare at it. Write the intermediate forms down one line at a time, and the step where your prediction diverged will be obvious.

42. Which expression gives 9?

Elimination

Only one of these evaluates to 9.

Eliminate the wrong options

Rule out the three that do not.

  • A. 1 + 2 * 3
  • B. (1 + 2) * 3
  • C. 1 + 2 + 3
  • D. 1 * 2 + 3

Survives elimination: B

Why: Parentheses are evaluated first, so 1 + 2 becomes 3 and then 3 * 3 is 9. Without the parentheses the same characters give 7, which is the clearest demonstration there is that parentheses change meaning rather than merely clarifying it.

43. Force the addition

Fill the middle

Make this print Total: 42 rather than Total: 2022.

Fill in the blanks

int a = 20, b = 22;
System.out.println("Total: " + (a + b));

Why: Wrapping a + b in parentheses makes the addition happen before the concatenation, so 42 is computed as a number and only then converted to text. Without them, the leftmost + sees a string on its left and concatenates each number in turn, giving 2022.

44. Do parentheses ever change nothing?

Edge cases

You have been told to add them freely. Find the cases where they are pure documentation.

Discussion prompt

Give an expression where adding parentheses changes the value, and one where it cannot possibly change the value no matter where you put them. What distinguishes the two?

Hint: Think about whether the operators involved have different precedence.

Answer:

Changes the value: 1 + 2 * 3 versus (1 + 2) * 3 — 7 against 9. The operators have different precedence, so grouping decides which happens first.

Cannot change it: 1 + 2 + 3. All the operators are addition, which is associative, so any grouping gives 6.

The distinguishing feature is whether regrouping changes which operation runs first and whether that matters. Note the second condition — minute * 100 / 60 has equal precedence throughout, yet grouping it as minute * (100 / 60) gives 59 instead of 98, because integer division is not associative once information is being discarded.

45. The three kinds of error

Section

Sections 2.9-2.10

46. Compile-time, run-time, and logic

Concept

Three kinds of error can occur in a program, and it is useful to distinguish among them in order to track them down more quickly. They differ in when they appear and in how much help you get.

Figure (svg): A pipeline showing where each kind of error appears: compile-time at javac, run-time while the program runs, and logic errors only visible in the output

compile-time error — An error that occurs when you violate the rules of the Java language. The program cannot be compiled.

run-time error — An error that does not appear until after the program has started running; also called an exception.

logic error — The program compiles and runs without any error message, but does not do the right thing.

The third one is the hard one, and Think Java is blunt about why: the compiler and the interpreter cannot help you, since they do not know what the right thing is.

47. What each kind of error looks like

Picture it

The symptom tells you which kind you have, and that tells you where to look.

Figure (svg): Two panels contrasting a compile error message from javac with a run-time exception trace

A logic error produces neither of these. It produces output — plausible, wrong output — and that is the only clue you get.

48. Reading a compiler message that points at the wrong line

Worked example

Error messages usually indicate where the error occurred. Sometimes they report where the error was detected, which is not the same place.

1  public class Hello {
2      public static void main(String[] args) {
3          // generate some simple output
4          System.out.println("Hello, World!");
5
6  }
what is wrongreported atactually on
missing semicolonline 5line 4 — usually accurate
missing closing brace for mainline 7 — end of fileline 5
the messagereached end of file while parsingwritten from the compiler's point of view

Read the message and the line number.

Why: reached end of file while parsing at line 7 — but the file has only 6 lines.

Recognise the vocabulary as the compiler's, not yours.

Why: Parsing is the process of reading a program before translating it. Reaching the end while still parsing means something was omitted.

Accept that the compiler does not know what or where.

Why: It only knows it ran out of file with work still open. The missing brace could be anywhere above.

Search backwards from the reported line.

Why: Check that every opening brace has a partner, working upward. Consistent indentation makes this a glance rather than a hunt.

Verify: Add the missing brace and recompile; the error disappears and no new one takes its place.

Why: The general lesson: error messages contain useful information, so read them — but do not take them too literally. The line number is where the compiler noticed.

49. Classify each error

Definition probe

Three kinds, distinguished by when they appear.

Sort into buckets

Sort each situation by the kind of error it is.

compile-time
a missing semicolon; using a variable that was never declared
run-time
dividing by zero while the program runs
logic
printing the average as 2 instead of 2.5; println where print was meant
ct
A violation of the rules of the language. javac refuses to build the program, so nothing runs.
rt
The program is legal and starts running, then something goes wrong and it terminates with an exception message.
lg
It compiles, it runs, it produces output — and the output is wrong. No tool reports anything, because no tool knows what you intended.

50. A logic error, in a program you already know

Concept

The third kind of error compiles and runs perfectly. Here is a version of Hello World with one, and nothing about running it will tell you.

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, ");
        System.out.println("World!");
    }
}
compiles?runs?outputcorrect?
this versionyesyesHello, / World! on two linesno, if one line was wanted
with print on line 3yesyesHello, World! on one lineyes

The problem is that the first line uses println when we probably meant print. The program does exactly what it was told to do — which is the defining feature of a logic error. Identifying them is hard because you have to work backwards from the output.

51. Assuming 'it compiled' means 'it works'

Trap

The trap

The assumption. No errors from javac, so the program is correct.

int total = 10;
int count = 4;
System.out.println("Average: " + total / count);
checkresult
does it compile?yes — no compile-time error
does it run?yes — no exception
is the output right?no — displays 'Average: 2', not 2.5

Two integer operands, so integer division, so the average is wrong by a quarter. Every tool in the toolchain reports success.

The fix

Compiling is a floor, not a ceiling. The tools check legality; only you check meaning.

int total = 10;
int count = 4;
System.out.println("Average: " + (double) total / count);
kind of errorwho catches it
compile-timejavac, before anything runs
run-timethe JVM, by crashing with a message
logicyou, by checking the output against what you expected

The cast (double) is Chapter 3's material; the point here is the third row of the table. Predicting the output before you run is the only defence against the third kind of error, which is why every worked example in this course ends with a verify step.

52. Reading an exception message

Notation

Run-time error messages are very useful for debugging. Every part of this one is telling you something.

Annotate

  • Exception in thread "main" — the error happened on the main thread, which for now is the only one your programs have.
  • java.lang.ArithmeticException — the NAME of the exception. This is the category, and it is what you would search for.
  • / by zero — the specific message, indicating more precisely what happened. The name tells you the kind, this tells you the instance.
  • at Hello.main — the method where the error occurred: the method main in the class Hello.
  • (Hello.java:5) — the file the method is defined in, and the line number. Unlike compile errors, this line number is where the error actually happened.

Four pieces of information in two lines: what kind, what specifically, which method, which line. Getting into the habit of reading all four is what turns a crash from alarming into informative.

53. Which kind is hardest, and why?

Hypothesis

A question about where your debugging time will actually go.

Predict first

Which of the three kinds of error is generally hardest to track down?

  • Logic errors, because no tool reports them and you must work backwards from the output
  • Compile-time errors, because the messages are confusing
  • Run-time errors, because the program crashes
  • They are all equally difficult

Correct: Logic errors, because no tool reports them and you must work backwards from the output

Why: Compile-time and run-time errors both announce themselves and name a line. A logic error announces nothing — the program does exactly what you told it to do — so you have to notice the output is wrong, reason backwards to a cause, and test the hypothesis. Think Java says it directly: the compiler and interpreter cannot help, since they do not know what the right thing is.

54. Find the logic error with no message to guide you

Constraint

Practise the backwards reasoning, on something small.

Discussion prompt

A program is supposed to print a student's average of 10 marks out of 4 tests. It prints Average: 2. It compiles and it does not crash. List the hypotheses you would test, in the order you would test them, and say how you would distinguish between them.

Hint: The output is not obviously nonsense — it is close to right, which is a clue in itself.

Answer:

First hypothesis: integer division. 10 / 4 is 2 by integer division and 2.5 exactly. The symptom — a result that is right but truncated — points straight here. Test it by printing 10.0 / 4.

Second: wrong operands. Perhaps count is not 4, or total is not 10. Test by printing both variables before the division.

Third: the wrong operator. Perhaps a - where a / was meant. Test by reading the line, which is cheap and should probably have been first.

The general method: the shape of the wrongness suggests the cause. Truncated-but-close means integer division; wildly wrong means wrong operands or operator; nothing at all means a missing println. Learning to read the symptom is most of debugging.

55. The three kinds of error, side by side

Comparison

Fill the blanks. This table is worth being able to reproduce from memory.

Comparison matrix

compile-timerun-timelogic
when it appearswhen you compilewhile the program is runningnever — you notice the output is wrong
does the program run?no, it is never builtit starts, then stopsyes, all the way through
do you get a message?yes, with a line numberyes, with an exception name and a lineno message at all
examplea missing semicolondividing by zeroprintln where print was meant

Read the bottom-right cell and remember that the program is doing exactly what you told it to. That is not a consolation — it is the diagnostic clue.

56. The pattern to carry away

Pattern

Three of this lesson's four surprises have the same shape: an operator behaves according to the types of its operands, and you were looking somewhere else.

the surprisewhere you were lookingwhere to look
double y = 1 / 3 gives 0.0the type of ythe types of 1 and 3
"n=" + 1 + 2 gives n=12the numbersthe leftmost operand, which is a string
0.1 added ten times is not 1.0the arithmetichow 0.1 is stored in binary
it compiled, so it worksthe compilerthe output, against your prediction

57. Check: which division

Check

Work it out before you click.

Check your understanding

What does System.out.println(7 / 2.0); display?

  • A. 3.5 (correct)
  • B. 3
  • C. 3.0
  • D. 4

Answer: A

Why: One operand is a double, which is enough to make Java perform floating-point division, so the exact result 3.5 is computed and displayed. Only when both operands are integers does Java truncate toward zero.

Why B tempts people
This would be the result of integer division, which requires BOTH operands to be integers — 2.0 is a double.
Why C tempts people
This would be integer division followed by a conversion to double, which is what happens when the result is assigned to a double, not what happens here.
Why D tempts people
Java never rounds to nearest in integer division; it truncates toward zero. And in any case this division is floating-point.

58. Check: the plus operator

Check

Work it out before you click.

int x = 5;
System.out.println("x + 1 = " + x + 1);
stepoperandsresult
1"x + 1 = " and x"x + 1 = 5"
2"x + 1 = 5" and 1"x + 1 = 51"

Check your understanding

What does this display?

  • A. x + 1 = 51 (correct)
  • B. x + 1 = 6
  • C. 5 + 1 = 6
  • D. x + 1 = 5 1

Answer: A

Why: The leftmost + has a string on its left, so it concatenates and turns 5 into text; the result is a string, so the next + concatenates the 1 as well. To display 6 you would need "x + 1 = " + (x + 1), which forces the addition before any concatenation.

Why B tempts people
This assumes the numbers are added, but once a string is the left operand every following + concatenates.
Why C tempts people
The text inside the quotation marks is printed literally — Java does not evaluate the characters x + 1 inside a string.
Why D tempts people
Concatenation joins end to end with nothing between; no space is inserted unless you put one in a string.

59. Check: classifying an error

Check

Work it out before you click.

int count = 0;
System.out.println(100 / count);
stageoutcome
compilesucceeds — dividing by a variable is legal
runthrows ArithmeticException: / by zero
outputa stack trace, then the program stops

Check your understanding

What kind of error is this?

  • A. A run-time error (correct)
  • B. A compile-time error
  • C. A logic error
  • D. No error — it prints infinity

Answer: A

Why: The code is legal Java, so it compiles; the problem only appears once the program is running and the interpreter tries to divide by zero, which throws an ArithmeticException and terminates the program. That is the definition of a run-time error.

Why B tempts people
Dividing by a variable is perfectly legal, and the compiler cannot know that count will be zero when the line runs.
Why C tempts people
A logic error produces wrong output without any message. Here the program crashes and names the problem explicitly.
Why D tempts people
Integer division by zero throws an exception in Java rather than producing infinity — that behaviour belongs to floating-point division.

60. The bug that costs real money

Real world

Rounding error is not a textbook curiosity. It has caused documented failures with serious consequences.

Discussion prompt

You now know that 0.1 cannot be stored exactly in binary, and that errors accumulate over repeated operations. Where in a real system would that be most dangerous — and what makes such a bug so hard to catch in testing?

Hint: Think about how long it takes for a tiny error to become visible.

Answer:

The dangerous places are systems that accumulate over long periods: financial ledgers, cumulative totals, and anything counting elapsed time in small increments. A single operation is fine; a million are not.

It is hard to catch in testing precisely because short tests do not accumulate enough error to be visible. A test that adds 0.1 ten times might pass; the same code running for a month does not. The Patriot missile failure at Dhahran in 1991 was caused by exactly this shape of problem — a small timing error that grew over a hundred hours of continuous operation.

The transferable habit: when you choose a floating-point type, ask how many operations the value will go through before anyone looks at it. If the answer is 'a lot', ask whether an integer count of the smallest unit would do instead.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

What does System.out.println(1 + 2 + "3" + 4 + 5); display?

  • 3345
  • 15
  • 12345
  • 3 345

Correct: 3345

Why: Evaluation is strictly left to right. 1 + 2 has two numbers so it adds, giving 3. Then 3 + "3" has a string operand so it concatenates, giving "33". From that point everything is a string, so 4 and 5 are appended as text: "334" then "3345". The rule is that the first string turns every subsequent + into concatenation — and everything before it was ordinary addition.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

Explain to a classmate why double y = 1 / 3; gives 0.0 rather than 0.333. They will object that y is clearly a double, so the division should produce a decimal. Answer that objection directly.

Hint: The order in which things happen is the whole answer.

Answer:

Java evaluates the right-hand side completely before it thinks about the variable at all. On the right there are two integers, so it does integer division and gets 0. Then it looks at y, sees a double, and converts 0 into 0.0. The type of y is real, but it arrives too late — the precision was already gone.

The objection is worth taking seriously rather than brushing aside, because it is based on a reasonable expectation: that a language would look at what you asked for. Java does not. It evaluates expressions bottom-up from their operands, and the destination has no influence on that.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

Your program compiles cleanly, runs to completion, and prints the wrong number. Which kind of error is it, and which tool will help you find it?

  • A logic error, and no tool will help — you must reason backwards from the output
  • A compile-time error, and javac will point at the line
  • A run-time error, and the exception message names the line
  • It cannot be an error, because it compiled and ran

Correct: A logic error, and no tool will help — you must reason backwards from the output

Why: Compiling and running successfully rules out the first two kinds entirely — those announce themselves. What is left is a logic error: the program did exactly what you told it to, which was not what you meant. The compiler and interpreter cannot help because neither knows what the right answer was, so the method is to compare the output against a prediction and work backwards to the first step where they diverge.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw a table with three columns headed compile-time, run-time and logic. In each column write: when the error appears, whether the program runs, what message you get, and one example from this lesson. Then, underneath, write out the four expressions 1 / 3, 1.0 / 3, "n" + 1 + 2 and "n" + (1 + 2) with their values, and one sentence saying what decides each one.

65. Recap

Recap

Five sections: two new capabilities, one set of rules for combining them, and the classification of errors you will use for the rest of the book.

if you remember one thingit is this
about typesthe operands decide, not the destination
about precisionmoney is counted, not measured
about errorsit compiled is not it works

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 2 (Variables and Operators), Sections 2.6-2.10, pp. 23-29
  2. The Java Tutorials — Primitive Data Types
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108