Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 2 – Variables, types and operators

Floating point, rounding and BigDecimal

Why 0.1 + 0.2 prints 0.30000000000000004 in Java, how to compare doubles and handle NaN, and how to compute money with BigDecimal and a RoundingMode.

  • Beginner
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain why 0.1 + 0.2 is not exactly 0.3 in double arithmetic
  • Compare doubles safely and handle NaN, infinities and -0.0
  • Predict what Math.round, Math.rint, %.2f and a cast to int do to a double
  • Calculate money with BigDecimal using an explicit scale and RoundingMode
  • Split a tax-inclusive price so that its two parts always add up to it

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain why 0.1 + 0.2 is not exactly 0.3 in double arithmetic
  • Compare doubles safely and handle NaN, infinities and -0.0
  • Predict what Math.round, Math.rint, %.2f and a cast to int do to a double
  • Calculate money with BigDecimal using an explicit scale and RoundingMode
  • Split a tax-inclusive price so that its two parts always add up to it

Before you start

On this page

In Java, 0.1 + 0.2 prints 0.30000000000000004, and 0.1 + 0.2 == 0.3 is false. Nothing is broken: a double is an IEEE 754: The standard for binary floating-point arithmetic that float and double follow: float is binary32 with 24 significant bits, double is binary64 with 53, and every operation rounds its exact result to the nearest representable value. binary64 number with 53 significant bits, and a decimal fraction such as 0.1 cannot be written in base 2 with finitely many digits, so Java rounds it to the closest double it can represent. The tiny differences that follow are normal and predictable.

For measurements and science that is fine, as long as you compare doubles with a tolerance and know what infinity and NaN mean. For money it is not: amounts must be exact and must round the way an invoice says, and that is the job of java.math.BigDecimal, a decimal number with a scale and a rounding mode that you choose. You will see the stored values on JDK 25, step through the sum that misses 0.3, compare and round doubles safely, and split a price into its base and its tax so that the parts always add up.

A double holds the nearest binary fraction

In base 2 the places after the point are worth 1/2, 1/4, 1/8, 1/16 and so on. A value such as 0.75, which is 1/2 plus 1/4, needs two of those places and is stored exactly. One tenth can never be built from them, because 10 is 2 × 5 and no power of two divides by 5, so its binary digits repeat for ever and the double keeps only the first 53 significant bits. new BigDecimal(double) converts a double without any rounding, so it prints the value the double really holds:

Stored.java Java
// What a double really holds: new BigDecimal(double) shows every digit of the stored value.
void main() {
    IO.println("0.1 + 0.2        = " + (0.1 + 0.2));
    IO.println("0.1 + 0.2 == 0.3 : " + (0.1 + 0.2 == 0.3));
    IO.println("stored 0.1       = " + new BigDecimal(0.1));
    IO.println("stored 0.2       = " + new BigDecimal(0.2));
    IO.println("their exact sum  = " + new BigDecimal(0.1).add(new BigDecimal(0.2)));
    IO.println("0.1 + 0.2 holds  = " + new BigDecimal(0.1 + 0.2));
    IO.println("stored 0.3       = " + new BigDecimal(0.3));
    IO.println("stored 19.99     = " + new BigDecimal(19.99));

    double sum = 0;
    for (int i = 0; i < 10; i++) sum += 0.1;
    IO.println("ten 0.1s added   = " + sum);

    IO.println("gap after 1.0    = " + Math.ulp(1.0));
    long big = 9_007_199_254_740_993L;            // 2 to the power 53, plus 1
    IO.println(big + " as a double = " + (double) big);
}
The values behind 0.1, 0.2, 0.3 and 19.99 Python · stored_session.py
from session import run, version

version()
run("""
java Stored.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Stored.java
0.1 + 0.2        = 0.30000000000000004
0.1 + 0.2 == 0.3 : false
stored 0.1       = 0.1000000000000000055511151231257827021181583404541015625
stored 0.2       = 0.200000000000000011102230246251565404236316680908203125
their exact sum  = 0.3000000000000000166533453693773481063544750213623046875
0.1 + 0.2 holds  = 0.3000000000000000444089209850062616169452667236328125
stored 0.3       = 0.299999999999999988897769753748434595763683319091796875
stored 19.99     = 19.989999999999998436805981327779591083526611328125
ten 0.1s added   = 0.9999999999999999
gap after 1.0    = 2.220446049250313E-16
9007199254740993 as a double = 9.007199254740992E15

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stored_session.py

The double for 0.1 is a little above 0.1 and the double for 0.2 a little above 0.2, so their sum lands on the double just above 0.3, while the literal 0.3 is stored as the double just below it. Two different doubles, so == is false. Step through the four values the run printed around 0.3:

Interactive · step by step Why 0.1 + 0.2 lands on a different double from 0.3 Step through the two stored values, their exact sum, the two doubles beside it and the one each side of == holds.
Why 0.1 + 0.2 lands on a different double from 0.3, step 6 of 6. Showing stored 0.1, stored 0.2, their exact sum, the double below, the double above, 0.3 in the code, 0.1 + 0.2. In focus: the double below, 0.3 in the code.rounds tostored 0.10.1000000000000000055…stored 0.20.2000000000000000111…their exact sum0.3000000000000000166…the double below0.2999999999999999888…the double above0.3000000000000000444…0.3 in the codestored as the one below0.1 + 0.2rounds to the one above

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. 0.1, stored. One tenth has no exact binary form, so 0.1 is stored as the nearest double, 0.1000000000000000055…, a little above 0.1.
  2. 0.2, stored. 0.2 is stored the same way, as 0.2000000000000000111…, also a little above.
  3. The exact sum. Added exactly, the two stored values make 0.3000000000000000166…, the line their exact sum of the run. That number is not a double either.
  4. Two neighbours. The doubles on each side of it are 0.2999999999999999888… and 0.3000000000000000444…, about 5.6 × 10⁻¹⁷ apart, and the exact sum lies exactly halfway between them.
  5. The tie is broken. An addition rounds its exact result to the nearest double, and a tie goes to the neighbour whose last bit is 0, here the one above. So 0.1 + 0.2 holds 0.3000000000000000444…, which prints as 0.30000000000000004.
  6. 0.3 is the other one. The literal 0.3 is stored as its own nearest double, the one below, as the line stored 0.3 shows. Two different doubles, so 0.1 + 0.2 == 0.3 is false.

Adding 0.1 ten times misses 1.0 the same way. Math.ulp(1.0) prints 2.220446049250313E-16: that is the ulp: Unit in the last place: the gap between a floating-point number and the next larger one of its type. Math.ulp(1.0) is 2.220446049250313E-16, and the gap grows with the size of the number. of 1.0, how far it is from its neighbour above, and the reason a double carries roughly 15 or 16 decimal digits that you can trust. Whole numbers are exact only up to 2⁵³: the last line shows 9,007,199,254,740,993 coming back as …992, the nearest double.

Printing hides all this. Double.toString writes the fewest digits that still identify the double uniquely, so the double for 0.1 comes out as 0.1 rather than its 55 exact digits after the point; the difference shows only once arithmetic lands on a neighbouring double, as 0.1 + 0.2 does.

IEEE 754 Floating-Point Converter Type 0.1 or 19.99 to see its 64 bits, the exact stored value and the rounding error.

Key idea

A double holds the binary fraction nearest to the decimal you wrote, so most decimals are a little off before any arithmetic starts, and every operation rounds its exact result to a double again.

Checkpoint

Check: What a double stores

  1. Question 1 of 2 Which of these literals is stored exactly as a double?

    Choose one answer.

    A hint

    Which one is a sum of halves, quarters, eighths and so on?

    Show the answer: Question 1 of 2

    Answer: 0.75

    A decimal is exact in a double only when it is a sum of powers of 1/2 that fits in 53 significant bits. 0.75 is; 0.1, 0.3 and 19.99 are not, and are stored as their nearest doubles.

  2. Question 2 of 2 Which statement about the double that 0.1 + 0.2 gives is true?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is the neighbour just above the double that the literal 0.3 is stored as

    Both stored values are a little high, their exact sum 0.3000000000000000166… is a tie between two doubles, and the tie goes to the one above. The literal 0.3 is stored as the one below, so == is false.

JDK 17 made arithmetic identical everywhere, and JDK 19 changed the printing

Two releases changed Java’s floating point, and code written for older JDKs still shows both. Walk through how the rules moved:

Timeline · 4 events in order How Java's floating point changed between releases Walk through the releases in order, with what each one changed for the results and for the printed digits.
  1. Java 1.0

    IEEE 754 and strict results from the start

    float and double were IEEE 754 binary32 and binary64 numbers, and every floating-point expression gave exactly the same result on every computer.

  2. Java SE 1.2

    A looser default mode, and strictfp strictfp added

    To make arithmetic on the x87 unit of x86 processors cheaper, the default rules were loosened, and the strictfp modifier was added for code that needed the original, exactly reproducible results.

  3. JDK 17

    Strict everywhere again strictfp redundant

    JEP 306 made every floating-point expression strict. Arithmetic results are bit for bit the same on every platform, strictfp changes nothing, and javac warns that it is not required.

  4. JDK 19

    The shortest digits when printing printing changed

    Double.toString and Float.toString print the shortest decimal that identifies the value. 2e23 prints as 2.0E23 where JDK 17 printed 1.9999999999999998E23; the double itself is the same.

The timeline has 4 events, from Java 1.0 to JDK 19.

Floating-point arithmetic in Java, the operators such as + and * and the conversions between types, gives bit-for-bit identical results on every platform. Before JDK 17 that was guaranteed only inside code marked strictfp; JEP 306 made every floating-point expression strict, so the modifier no longer changes anything and javac says so:

Strict.java Java
// strictfp was needed before JDK 17 for results that are the same on every machine; now it changes nothing.
strictfp double area(double radius) {
    return Math.PI * radius * radius;
}

void main() {
    IO.println(area(2.5));
}
javac warns that strictfp is not needed Python · strict_session.py
from session import run, version

version()
run("""
javac Strict.java
java Strict
""")

Output

# openjdk version "25.0.4.1" LTS
$ javac Strict.java
Strict.java:2: warning: [strictfp] as of release 17, all floating-point expressions are evaluated strictly and 'strictfp' is not required
strictfp double area(double radius) {
                ^
1 warning
$ java Strict
19.634954084936208

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 strict_session.py

You can delete strictfp from old code without changing a single result. The methods of Math, such as Math.sin, are a different matter: their documentation allows the last bit to differ from one platform to another, while StrictMath.sin is defined to give the same result everywhere.

Double.toString has followed the shortest-digits rule exactly since JDK 19. Older releases sometimes printed a longer string than needed, with different last digits. The same class, run on JDK 17 and then on JDK 25:

Shortest.java Java
public class Shortest {
    public static void main(String[] args) {
        System.out.println(2e23 + " " + 1e23 + " " + 0.1);
    }
}
Double.toString on JDK 17 and on JDK 25 Python · shortest_session.py
from session import run, version

version("17")
run("java Shortest.java", jdk="17")
version()
run("java Shortest.java")

Output

# openjdk version "17.0.20.1"
$ java Shortest.java
1.9999999999999998E23 9.999999999999999E22 0.1
# openjdk version "25.0.4.1" LTS
$ java Shortest.java
2.0E23 1.0E23 0.1

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shortest_session.py

The doubles are the same on both; only the printed text changed. If you compare printed doubles in tests, for example after an upgrade from JDK 17, expect a few of them to look different.

JDK 17 and JDK 19

From JDK 17, strictfp changes no result and javac warns that it is not required. From JDK 19, 2e23 prints as 2.0E23, where JDK 17 printed 1.9999999999999998E23 for the same double.

Checkpoint

Check: Strict results and shortest printing

  1. Question 1 of 2 On JDK 25, what does the strictfp modifier change about a method's results?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing, and javac warns that it is not required

    strictfp was needed before JDK 17 for results that are identical everywhere. Now every expression is strict, and javac says as of release 17, all floating-point expressions are evaluated strictly.

  2. Question 2 of 2 Shortest.java printed 1.9999999999999998E23 on JDK 17 and 2.0E23 on JDK 25 for the literal 2e23. What changed between the two?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Only the printed digits; the double is the same

    The double nearest 2e23 is the same on both JDKs. JDK 19 changed Double.toString to print the shortest digits that still identify it, so tests that compare printed doubles may see a different text after an upgrade.

Compare computed doubles with a tolerance

A computed double usually carries some rounding error, so testing it with == demands more precision than the arithmetic delivered. Test whether the two values are near enough to each other instead. Java’s standard library has no ready-made method for this, so write a small one:

Close.java Java
// Comparing computed doubles: a relative tolerance, plus an absolute one for values near zero.
boolean close(double a, double b, double relTol, double absTol) {
    return Math.abs(a - b) <= Math.max(relTol * Math.max(Math.abs(a), Math.abs(b)), absTol);
}

void main() {
    double total = 0.1 + 0.2;
    double balance = 0.1 + 0.2 - 0.3;             // should be zero
    IO.println("total == 0.3            : " + (total == 0.3));
    IO.println("close(total, 0.3)       : " + close(total, 0.3, 1e-9, 0));
    IO.println("balance                 = " + balance);
    IO.println("close(balance, 0), rel  : " + close(balance, 0, 1e-9, 0));
    IO.println("close(balance, 0), abs  : " + close(balance, 0, 1e-9, 1e-9));
    IO.println("difference from 0.3     = " + Math.abs(total - 0.3));
    IO.println("relative allowance, 0.3 = " + 1e-9 * Math.max(Math.abs(total), 0.3));
    IO.println("relative allowance, 0   = " + 1e-9 * Math.max(Math.abs(balance), 0));
}
Comparing with a tolerance, run with JDK 25 Python · close_session.py
from session import run, version

version()
run("""
java Close.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Close.java
total == 0.3            : false
close(total, 0.3)       : true
balance                 = 5.551115123125783E-17
close(balance, 0), rel  : false
close(balance, 0), abs  : true
difference from 0.3     = 5.551115123125783E-17
relative allowance, 0.3 = 3.0000000000000005E-10
relative allowance, 0   = 5.551115123125783E-26

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 close_session.py

close measures the difference against the bigger of the two magnitudes. With relTol at 1e-9, two values pass when they agree to roughly nine significant digits, whether they are tiny or huge. The idea breaks down next to zero: a relative test scales the allowed difference by the size of the numbers, and when the expected value is 0 the allowed difference shrinks to nothing. So balance, which ought to be 0 but holds 5.6 × 10⁻¹⁷, is rejected. Step through the three calls with the numbers the last three lines of the run printed:

Interactive · stage by stage How close() decides whether two doubles are near enough Step through the test for total against 0.3, then for balance against 0, first with a relative tolerance only and then with an absolute one.
How close() decides whether two doubles are near enough, step 6 of 6. Showing The difference (5.551115123125783E-17), The relative allowance (5.551115123125783E-26), The absolute allowance (1e-9), Take the larger allowance (1e-9), Compare with it (true with absTol 1e-9). In focus: The absolute allowance, Take the larger allowance, Compare with it.The difference5.551115123125783E-17The relative allowance5.551115123125783E-26The absolute allowance1e-9Take the larger allowance1e-9Compare with ittrue with absTol 1e-9

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. total against 0.3. close(total, 0.3, 1e-9, 0) starts with the difference: total is 0.30000000000000004, so |total − 0.3| is 5.551115123125783E-17, as the run printed.
  2. The relative allowance. The allowance grows with the numbers compared. 1e-9 times the larger of the two, about 0.3, is 3.0000000000000005E-10, millions of times more than the difference.
  3. Close. absTol is 0 here, so the relative allowance decides, and the difference is far below it. close(total, 0.3) is true.
  4. balance against 0. balance should be 0 and holds 5.551115123125783E-17. Against 0 the larger size is that tiny number itself, so the relative allowance shrinks to 5.551115123125783E-26.
  5. Not close. With absTol at 0 the allowance is smaller than the difference, so close(balance, 0, 1e-9, 0) is false. A relative test cannot pass a value that ought to be 0.
  6. An absolute allowance. close(balance, 0, 1e-9, 1e-9) adds an absolute allowance of 1e-9, a difference accepted whatever the sizes. Now the larger allowance is 1e-9, and the test is true.

The absTol argument covers the case near zero: it is a difference you accept whatever the sizes. Pick it from what the numbers mean; for lengths in metres, 1e-9 is one nanometre.

Testing a computed double with ==

if (total == 0.3) is false for total = 0.1 + 0.2, although the arithmetic did nothing wrong. Write close(total, 0.3, 1e-9, 1e-9), with tolerances chosen for what the numbers mean.

Checkpoint

Check: Comparing with a tolerance

  1. Question 1 of 2 Why is close(balance, 0, 1e-9, 0) false, although balance is only 5.551115123125783E-17?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Against 0 the relative allowance is 1e-9 times that tiny value, 5.551115123125783E-26, smaller than the difference

    The difference is 5.551115123125783E-17, and the relative allowance is 1e-9 × 5.551115123125783E-17. Only an absolute tolerance, a difference accepted whatever the sizes, can pass a value that ought to be 0.

  2. Question 2 of 2 Is close(1_000_000, 1_000_000.0005, 1e-9, 0) true?

    Choose one answer.

    A hint

    The relative allowance is relTol times the larger of the two numbers.

    Show the answer: Question 2 of 2

    Answer: Yes, because the difference 0.0005 is below the allowance 1e-9 × 1,000,000.0005, about 0.001

    relTol × max(|a|, |b|) = 1e-9 × 1,000,000.0005 ≈ 0.001, and 0.0005 is below it, so the values agree to about nine significant digits and close returns true.

Floating point never throws: infinity, NaN and minus zero

Floating-point arithmetic never throws. A result that is too large becomes infinity, and an operation with no sensible answer gives NaN: Not a Number: the double or float that 0.0 / 0.0 and Math.sqrt(-1) give. Every comparison with NaN is false, itself included, so it is found with Double.isNaN(x), never with ==., “not a number”:

Specials.java Java
// Infinity, NaN and the two zeros: double arithmetic never throws.
void main() {
    double nan = 0.0 / 0.0;
    IO.println("1.0 / 0      = " + 1.0 / 0 + ",  -1.0 / 0 = " + -1.0 / 0);
    IO.println("0.0 / 0.0    = " + nan + ",  Math.sqrt(-1) = " + Math.sqrt(-1));
    IO.println("nan == nan   : " + (nan == nan) + ",  Double.isNaN(nan): " + Double.isNaN(nan));
    IO.println("Double.compare(nan, nan) = " + Double.compare(nan, nan)
            + ",  Double.valueOf(nan).equals(nan): " + Double.valueOf(nan).equals(nan));
    IO.println("0.0 == -0.0  : " + (0.0 == -0.0) + ",  Double.compare(0.0, -0.0) = " + Double.compare(0.0, -0.0)
            + ",  1 / -0.0 = " + 1 / -0.0);
    IO.println("MAX_VALUE * 2 = " + Double.MAX_VALUE * 2 + ",  MAX_VALUE + 1 == MAX_VALUE: " + (Double.MAX_VALUE + 1 == Double.MAX_VALUE));
}
The special values, run with JDK 25 Python · specials_session.py
from session import run, version

version()
run("""
java Specials.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Specials.java
1.0 / 0      = Infinity,  -1.0 / 0 = -Infinity
0.0 / 0.0    = NaN,  Math.sqrt(-1) = NaN
nan == nan   : false,  Double.isNaN(nan): true
Double.compare(nan, nan) = 0,  Double.valueOf(nan).equals(nan): true
0.0 == -0.0  : true,  Double.compare(0.0, -0.0) = 1,  1 / -0.0 = -Infinity
MAX_VALUE * 2 = Infinity,  MAX_VALUE + 1 == MAX_VALUE: true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 specials_session.py

  • 1.0 / 0 is Infinity, while the integer division 1 / 0 throws. Double.MAX_VALUE * 2 overflows to Infinity, and Double.MAX_VALUE + 1 is still Double.MAX_VALUE, because 1 is far smaller than the gap between doubles there.
  • 0.0 / 0.0 and Math.sqrt(-1) are NaN. NaN compares false with everything, itself included, so nan == nan is false; test for it with Double.isNaN. Double.compare and Double.valueOf(nan).equals treat NaN as equal to itself, which keeps sorting and hash maps working.
  • -0.0 == 0.0 is true, but the two zeros differ: 1 / -0.0 is negative infinity, and Double.compare(0.0, -0.0) is 1. A sort puts -0.0 before 0.0.

The run shows two ways of comparing that disagree on exactly these values. Read the two cases side by side:

Compared == says Double.compare says
NaN with NaN false, not equal 0, equal
0.0 with -0.0 true, equal 1, 0.0 is the larger

== follows IEEE 754, which a sort or a hash map cannot use; Double.compare and equals give every double one place in a single order.

Checkpoint

Check: Infinity, NaN and minus zero

  1. Question 1 of 2 Which test is true when x is NaN, and false for every other double?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Double.isNaN(x)

    NaN compares false with everything, itself included, so == can never find it. Double.isNaN(x) is the test to use.

  2. Question 2 of 2 What is Double.MAX_VALUE + 1 == Double.MAX_VALUE?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: true

    The run printed MAX_VALUE + 1 == MAX_VALUE: true. The gap between doubles grows with their size, and 1 is far below half the gap there, so the sum rounds back to MAX_VALUE.

Each way of rounding a double has its own rule

Math.round, Math.rint, Math.floor, Math.ceil, %.2f and a cast to int each turn a double into fewer digits, and no two of them follow the same rule. The program runs them on the same values:

Rounding.java Java
// Ways to round a double, and the one that quietly cuts instead.
void main() {
    IO.println("Math.round(2.5) = " + Math.round(2.5) + ", Math.round(-2.5) = " + Math.round(-2.5));
    IO.println("Math.rint(2.5)  = " + Math.rint(2.5) + ", Math.rint(3.5) = " + Math.rint(3.5));
    IO.println("Math.floor(-2.5) = " + Math.floor(-2.5) + ", Math.ceil(-2.5) = " + Math.ceil(-2.5));
    IO.println("%.2f of 2.675   = " + String.format("%.2f", 2.675));
    IO.println("stored 2.675    = " + new BigDecimal(2.675));
    IO.println("new BigDecimal(2.675), 2 places, HALF_UP     = " + new BigDecimal(2.675).setScale(2, RoundingMode.HALF_UP));
    IO.println("BigDecimal.valueOf(2.675), 2 places, HALF_UP = " + BigDecimal.valueOf(2.675).setScale(2, RoundingMode.HALF_UP));
    IO.println("0.57 * 100 = " + 0.57 * 100 + ", (int) gives " + (int) (0.57 * 100) + ", Math.round gives " + Math.round(0.57 * 100));
}
Rounding a double several ways, and a cast that cuts Python · rounding_session.py
from session import run, version

version()
run("""
java Rounding.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Rounding.java
Math.round(2.5) = 3, Math.round(-2.5) = -2
Math.rint(2.5)  = 2.0, Math.rint(3.5) = 4.0
Math.floor(-2.5) = -3.0, Math.ceil(-2.5) = -2.0
%.2f of 2.675   = 2.68
stored 2.675    = 2.67499999999999982236431605997495353221893310546875
new BigDecimal(2.675), 2 places, HALF_UP     = 2.67
BigDecimal.valueOf(2.675), 2 places, HALF_UP = 2.68
0.57 * 100 = 56.99999999999999, (int) gives 56, Math.round gives 57

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounding_session.py

  • Math.round returns the nearest long and sends a tie upwards, toward positive infinity: Math.round(2.5) is 3 and Math.round(-2.5) is -2.
  • Math.rint sends a tie to the even neighbour, so 2.5 becomes 2.0 and 3.5 becomes 4.0. Math.floor rounds down and Math.ceil rounds up.
  • String.format("%.2f", 2.675) gives 2.68. Java’s formatter rounds the shortest decimal form of the double, the digits Double.toString prints, with the half-up rule, so it sees “2.675” and rounds up. The exact stored value is a little below 2.675, so rounding that value gives 2.67, which is what new BigDecimal(2.675) does, while BigDecimal.valueOf(2.675) starts from the digits “2.675” and gives 2.68.
  • 0.57 * 100 is 56.99999999999999, and a cast to int cuts the fraction off, giving 56. Use Math.round when a floating-point result has to become a whole number.

The third point is the one that surprises people. Step through what each of the three calls reads of 2.675 before it rounds:

Interactive · step by step Three roundings of 2.675, two different answers Step through the double that 2.675 becomes, then what each of the three calls reads of it before rounding.
Three roundings of 2.675, two different answers, step 6 of 6. Showing 2.675 in the code, the double it holds, %.2f, 2.68, new BigDecimal(2.675), 2.67, valueOf(2.675), 2.68. In focus: new BigDecimal(2.675), valueOf(2.675).stored as2.675 in the codea decimal literalthe double it holds2.67499999999999982…%.2freads the digits 2.6752.68rounded half upnew BigDecimal(2.675)reads the stored value2.67the third decimal is a 4valueOf(2.675)BigDecimal, from the digits2.68rounded half up

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. The literal. 2.675 in the code is a decimal, but the double nearest it is 2.67499999999999982…, a little below 2.675, as the line stored 2.675 of the run shows.
  2. One double, two readings. Every rounding below starts from that one double. What differs is what it reads of it, the exact stored value or the short digits Double.toString prints for it, 2.675.
  3. %.2f. String.format rounds the shortest decimal form, the digits 2.675, with the half-up rule. The third decimal is a 5, so it rounds up and prints 2.68.
  4. The exact value. new BigDecimal(2.675) copies the stored value exactly, 2.67499999…, and rounding that to 2 places with HALF_UP gives 2.67, because its third decimal is a 4.
  5. From the digits. BigDecimal.valueOf(2.675) goes through Double.toString, so it starts from the digits 2.675 and rounds to 2.68, the same answer as %.2f.
  6. Choose what is rounded. When a rule says how to round, start from the decimal itself, new BigDecimal("2.675") or BigDecimal.valueOf, never from new BigDecimal(double), which rounds the binary value.

Rounding Calculator Round any number with exact decimal arithmetic and see every rounding mode side by side.

Checkpoint

Check: Rounding a double

  1. Question 1 of 2 What does Math.round(-2.5) return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: -2

    Math.round(x) is the nearest long, with ties going toward positive infinity: Math.round(2.5) is 3 and Math.round(-2.5) is -2, as the run printed.

  2. Question 2 of 2 What does Math.rint(2.5) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 2.0

    Math.rint rounds to the nearest whole number and breaks ties toward the even one: 2.5 becomes 2.0 and 3.5 becomes 4.0, as the run printed.

BigDecimal keeps decimals exact with a scale and a rounding mode

A BigDecimal stores a whole number of any size, its unscaled value, and a scale: The number of digits after the decimal point of a BigDecimal: 19.99 has scale 2. A BigDecimal is its unscaled whole number times 10 to the power minus its scale, so 2.0 and 2.00 have the same value but different scales., the number of digits after the decimal point. The diagram shows both parts for 19.99, 2.0 and 2.00:

Three BigDecimals as an unscaled value and a scale: 19.99 is 1999 and 2, 2.0 is 20 and 1, and 2.00 is 200 and 2.A BigDecimal is a whole number and a scalevalue = unscaled value × 10 to the power −scalewritten asunscaled valuescalevaluenew BigDecimal("19.99")1999219.99new BigDecimal("2.0")2012.0new BigDecimal("2.00")20022.00same value,two scalescompareTo: 2.0 and 2.00 are equal · equals: they are not, the scales differ

A BigDecimal stores a whole number and how many digits stand after the point

Text description of the diagram

A BigDecimal's value is its unscaled value times 10 to the power minus its scale. Three examples:

  • `new BigDecimal("19.99")`: unscaled value 1999, scale 2, so the value is exactly 19.99.
  • `new BigDecimal("2.0")`: unscaled value 20, scale 1, value 2.0.
  • `new BigDecimal("2.00")`: unscaled value 200, scale 2, value 2.00.

The last two have the same value and different scales. `compareTo` looks at the value only, so it says they are equal; `equals` also compares the scale, so it says they are not.

Because it counts in powers of 10, every decimal amount you can write is exact. Addition, subtraction and multiplication are always exact; division and rounding need you to say how many digits to keep and which rounding mode: The rule a BigDecimal follows when it must drop digits, given as a RoundingMode: HALF_UP sends a tie away from zero, HALF_EVEN to the even neighbour, DOWN cuts toward zero, and UNNECESSARY throws if any digit would be lost. to use:

Money.java and Equality.java

Java (Money.java) · Money.java

// Decimal arithmetic with BigDecimal: exact sums, an explicit scale and rounding mode for division.
void main() {
    var price = new BigDecimal("19.99");
    IO.println("3 × 19.99          = " + price.multiply(BigDecimal.valueOf(3)));
    IO.println("0.1 + 0.2          = " + new BigDecimal("0.1").add(new BigDecimal("0.2")));
    IO.println("new BigDecimal(0.1) = " + new BigDecimal(0.1));
    IO.println("BigDecimal.valueOf(0.1) = " + BigDecimal.valueOf(0.1));

    var bill = new BigDecimal("100.00");
    try {
        IO.println(bill.divide(BigDecimal.valueOf(3)));
    } catch (ArithmeticException e) {
        IO.println("100.00 / 3: " + e.getMessage());
    }
    var share = bill.divide(BigDecimal.valueOf(3), 2, RoundingMode.DOWN);
    var last = bill.subtract(share.multiply(BigDecimal.valueOf(2)));
    IO.println("three shares: " + share + " + " + share + " + " + last);

    var a = new BigDecimal("2.345");
    IO.println("2.345 HALF_UP " + a.setScale(2, RoundingMode.HALF_UP) + ", HALF_EVEN " + a.setScale(2, RoundingMode.HALF_EVEN)
            + ", DOWN " + a.setScale(2, RoundingMode.DOWN));
}

Java (Equality.java) · Equality.java

// equals compares value and scale; compareTo compares value only.
void main() {
    var two = new BigDecimal("2.0");
    var twoAgain = new BigDecimal("2.00");
    IO.println("scales: " + two.scale() + " and " + twoAgain.scale());
    IO.println("equals: " + two.equals(twoAgain) + ", compareTo: " + two.compareTo(twoAgain));
    IO.println("in a HashSet: " + new HashSet<>(List.of(two, twoAgain)).size() + " elements");
    IO.println("in a TreeSet: " + new TreeSet<>(List.of(two, twoAgain)).size() + " element");
    IO.println("after stripTrailingZeros: " + two.stripTrailingZeros().equals(twoAgain.stripTrailingZeros()));
}
BigDecimal arithmetic, then equals and compareTo Python · money_session.py
from session import run, version

version()
run("""
java Money.java
java Equality.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java Money.java
3 × 19.99          = 59.97
0.1 + 0.2          = 0.3
new BigDecimal(0.1) = 0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1) = 0.1
100.00 / 3: Non-terminating decimal expansion; no exact representable decimal result.
three shares: 33.33 + 33.33 + 33.34
2.345 HALF_UP 2.35, HALF_EVEN 2.34, DOWN 2.34
$ java Equality.java
scales: 1 and 2
equals: false, compareTo: 0
in a HashSet: 2 elements
in a TreeSet: 1 element
after stripTrailingZeros: true

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 money_session.py

  • Build a BigDecimal from a string, new BigDecimal("19.99"), or from a whole number. new BigDecimal(0.1) copies the double exactly, error and all; BigDecimal.valueOf(0.1) goes through Double.toString and gives 0.1.
  • Divide with a scale and a rounding mode. bill.divide(three) throws, because 100.00 / 3 has no finite decimal form. divide(three, 2, RoundingMode.DOWN) gives 33.33, and the last share takes what is left, 33.34, so the three shares add up to the bill.
  • Choose the rounding mode on purpose. HALF_UP is the school rule; HALF_EVEN, sometimes called banker’s rounding, sends ties to the even neighbour, so that rounding many values does not drift upwards; DOWN cuts. The rule for an invoice or a tax comes from the business or the law, so check which one applies.
  • equals compares the scale too. 2.0 has scale 1 and 2.00 scale 2, so equals says they differ, while compareTo compares values and returns 0. A HashSet, which uses equals, keeps both; a TreeSet, which uses compareTo, keeps one. Compare amounts with compareTo, or bring them to one scale first.

The table puts the three ways of storing an amount side by side:

double, BigDecimal or a long of cents?
Criterion doubleBigDecimallong in cents
Stores 53 significant bits and a binary exponenta whole number and a decimal scale, any sizea whole number of the smallest unit
Exact for halves, quarters and whole numbers up to 2⁵³every decimal amountevery amount in whole cents
Speed and memory fast, 8 bytesslower, an object per valuefast, 8 bytes
Rounding IEEE 754 round to nearest, every operationonly where you ask, with the mode you chooseinteger division cuts; you round by hand
When to choose Measurements, science, graphics and statisticsPrices, taxes, interest and anything an invoice showsFast totals of money where every amount is whole cents

The rule of thumb

Use double for measurements, BigDecimal built from strings for any amount a rule says how to round, and a long of cents for fast totals of whole cents.

Checkpoint

Check: BigDecimal, scale and rounding mode

  1. Question 1 of 2 What does new BigDecimal("10.00").divide(BigDecimal.valueOf(4)) give?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 2.50

    divide(divisor) returns the exact quotient when there is one. 10.00 / 4 = 2.50 ends; 100.00 / 3 does not, which is why Money.java's bill.divide(three) throws and needs a scale and a RoundingMode.

  2. Question 2 of 2 What does new BigDecimal("7.50").equals(new BigDecimal("7.5")) return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: false, because the scales differ, 2 and 1

    Equality.java shows the same with 2.0 and 2.00: equals is false and compareTo is 0. Compare amounts with compareTo, or bring them to one scale first.

A tax must be what is left of the price

A price that includes a sales tax, VAT or GST has to be split into the price before tax and the tax itself. It is tempting to compute both parts and round each one. The run below does that at a rate of 12 % for every price from 0.01 to 1,000.00:

NaiveSplit.java Java
// Rounding the base and the tax separately: how often do they still add up to the price?
void main() {
    var hundred = BigDecimal.valueOf(100);
    var rate = BigDecimal.valueOf(12);
    var divisor = hundred.add(rate);
    int wrong = 0;
    BigDecimal first = null;
    for (int cents = 1; cents <= 100_000; cents++) {
        var price = BigDecimal.valueOf(cents, 2);
        var base = price.multiply(hundred).divide(divisor, 2, RoundingMode.HALF_UP);
        var tax = price.multiply(rate).divide(divisor, 2, RoundingMode.HALF_UP);
        if (base.add(tax).compareTo(price) != 0) {
            wrong++;
            if (first == null) first = price;
        }
    }
    IO.println(wrong + " of 100000 prices from 0.01 to 1000.00 do not add up at 12 %");
    var base = first.multiply(hundred).divide(divisor, 2, RoundingMode.HALF_UP);
    var tax = first.multiply(rate).divide(divisor, 2, RoundingMode.HALF_UP);
    IO.println("the first is " + first + ": base " + base + " + tax " + tax + " = " + base.add(tax));
    var exactBase = first.multiply(hundred).divide(divisor, MathContext.DECIMAL64);
    var exactTax = first.multiply(rate).divide(divisor, MathContext.DECIMAL64);
    IO.println("its exact parts: base " + exactBase + " + tax " + exactTax);
}
Rounding both parts separately Python · naive_session.py
from session import run, version

version()
run("""
java NaiveSplit.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java NaiveSplit.java
3571 of 100000 prices from 0.01 to 1000.00 do not add up at 12 %
the first is 0.14: base 0.13 + tax 0.02 = 0.15
its exact parts: base 0.125 + tax 0.015

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 naive_session.py

For 3,571 of the 100,000 prices, the two rounded parts do not add up to the price, the first already at 0.14. Step through that first price, from its exact parts to the cent that appears from nowhere:

Interactive · step by step Why 0.14 splits into 0.13 and 0.02 Step through the exact base and tax of 0.14 at 12 %, the two roundings, and the split that always adds up.
Why 0.14 splits into 0.13 and 0.02, step 5 of 5. Showing price 0.14, base, exact, tax, exact, base 0.13, tax 0.02, 0.13 + 0.02 = 0.15, tax = 0.14 − 0.13. In focus: tax = 0.14 − 0.13.price 0.1412 % tax includedbase, exact0.14 × 100 / 112 = 0.125tax, exact0.14 × 12 / 112 = 0.015base 0.130.125 rounded half uptax 0.020.015 rounded half up0.13 + 0.02 = 0.15one cent too manytax = 0.14 − 0.130.01, what is left

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The first price that fails. 0.14 is the first price of the run whose two rounded parts do not add up. It includes a tax of 12 %, so it is 112 % of the price before tax.
  2. Both parts, exactly. Exactly, the base is 0.14 × 100 / 112 = 0.125 and the tax 0.14 × 12 / 112 = 0.015, as the last line of the run printed, and 0.125 + 0.015 is 0.14.
  3. Two ties. Both exact parts end in a 5 at the third decimal place, so HALF_UP rounds each one up, the base to 0.13 and the tax to 0.02.
  4. One cent too many. Rounded separately, the parts make 0.13 + 0.02 = 0.15, one cent more than the price. 3,571 of the 100,000 prices of the run go wrong in this way.
  5. Round one part only. The fix rounds the base alone and makes the tax what is left, 0.14 − 0.13 = 0.01. The two parts then add up to the price every time, which the exercise below checks for every price up to 1,000.00.

The fix is to round one part and make the other what is left, which the exercise below asks you to write.

GST-inclusive prices

A GST-inclusive price splits into its taxable value and the tax: at 18 %, ₹118.00 is ₹100.00 plus ₹18.00 of tax. The rate depends on what is sold and is revised on the recommendation of the GST Council, so pass it in as a parameter instead of writing it into the code.

GST Calculator (India) Add or remove GST from a price and see the taxable value and the tax side by side.

Checkpoint

Check: Splitting an amount so the parts add up

  1. One question The price 0.14 includes 12 % tax, and its base is rounded to 0.13. Which tax makes the two parts add up to the price?

    Choose one answer.

    Show the answer: One question

    Answer: 0.01, the price minus the base

    Round one part only and make the other what is left: base 0.13, tax 0.14 − 0.13 = 0.01. Both rounded on their own gave 0.13 + 0.02 = 0.15.

Practise: split a tax-inclusive price

Write split(inclusive, ratePercent), which returns the price before tax and the tax, both with two decimal places. Round the base with RoundingMode.HALF_UP, then make the tax whatever is left, so that the two always add up to the price. The last check tries every price from 0.01 to 1,000.00 at 12 %:

TaxSplit.java Java
// Exercise: split(inclusive, ratePercent) splits a price that includes a sales tax (VAT or GST) into the price
// before tax and the tax, both with 2 decimal places. Round the base with HALF_UP, then make the tax what is left,
// so that base + tax is always exactly the price. Write it, then run this file: every check must say ok.
record Split(BigDecimal base, BigDecimal tax) {}

Split split(BigDecimal inclusive, int ratePercent) {
    return new Split(BigDecimal.ZERO, BigDecimal.ZERO);   // replace this line
}

int failures = 0;

void check(String price, int rate, String base, String tax) {
    Split s = split(new BigDecimal(price), rate);
    boolean passed = s.base().equals(new BigDecimal(base)) && s.tax().equals(new BigDecimal(tax));
    if (!passed) failures++;
    IO.println((passed ? "ok      " : "FAILED  ") + price + " at " + rate + " %: " + s.base() + " + " + s.tax());
}

void main() {
    check("118.00", 18, "100.00", "18.00");
    check("99.99", 18, "84.74", "15.25");
    check("1000.00", 5, "952.38", "47.62");
    check("100", 18, "84.75", "15.25");
    check("59.00", 0, "59.00", "0.00");
    check("0.00", 18, "0.00", "0.00");
    check("-118.00", 18, "-100.00", "-18.00");
    int bad = 0;
    for (int cents = 1; cents <= 100_000; cents++) {
        var price = BigDecimal.valueOf(cents, 2);
        Split s = split(price, 12);
        if (s.base().add(s.tax()).compareTo(price) != 0) bad++;
    }
    if (bad > 0) failures++;
    IO.println((bad == 0 ? "ok      " : "FAILED  ") + "base + tax is the price for every price from 0.01 to 1000.00 (" + bad + " wrong)");
    IO.println(failures + " of 8 checks failed");
    if (failures > 0) System.exit(1);
}

As given, the method returns two zeros:

The exercise before you start Python · exercise_session.py
from session import run, version

version()
run("""
!java TaxSplit.java
""")

Output

# openjdk version "25.0.4.1" LTS
$ java TaxSplit.java
FAILED  118.00 at 18 %: 0 + 0
FAILED  99.99 at 18 %: 0 + 0
FAILED  1000.00 at 5 %: 0 + 0
FAILED  100 at 18 %: 0 + 0
FAILED  59.00 at 0 %: 0 + 0
FAILED  0.00 at 18 %: 0 + 0
FAILED  -118.00 at 18 %: 0 + 0
FAILED  base + tax is the price for every price from 0.01 to 1000.00 (100000 wrong)
8 of 8 checks failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

First hint: the base is inclusive × 100 / (100 + rate), and divide(divisor, 2, RoundingMode.HALF_UP) gives it two decimal places. The checks compare both parts with equals, which looks at the scale too, and inclusive.subtract(base) keeps the base’s two places: even a price written as “100” gives the tax 15.25.

With all eight checks at ok, practise the seven ideas of this lesson, from stored values to the tax split. Questions you miss come back before the end, and each one explains its answer as soon as you check it:

Practice that adapts to you

Practise this lesson

20 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 20 Why is 0.75 stored exactly in a double, while 0.1 is not?

    Choose one answer.

    Show the answer: Question 1 of 20

    Answer: 0.75 is 1/2 + 1/4, a sum of binary places, while one tenth repeats for ever in binary

    0.75 = 1/2 + 1/4 needs two binary places. 0.1 needs infinitely many, so the double keeps the first 53 significant bits and holds 0.1000000000000000055…

  2. Question 2 of 20 Why is 0.1 + 0.2 == 0.3 false in Java?

    Choose one answer.

    Show the answer: Question 2 of 20

    Answer: 0.1 and 0.2 are stored as the nearest binary fractions, and their sum rounds to a different double from the one 0.3 is stored as

    A double is a binary fraction with 53 significant bits, and 1/10 has no exact binary form. The stored 0.1 and 0.2 are both a little high, so their sum lands on the double just above 0.3, while 0.3 itself is the double just below. Since JDK 17 the result is the same on every processor.

  3. Question 3 of 20 0.1f + 0.2f == 0.3f is true in Java, with float values. What does that tell you?

    Choose one answer.

    Show the answer: Question 3 of 20

    Answer: The float sum happens to round to the same float as 0.3f, so == worked by luck, not by exactness

    Each value is still the nearest float, not the decimal. For these three the rounding errors cancel, so the sum equals 0.3f, while in double they do not. Equality that depends on such luck is no test; compare computed values with a tolerance.

  4. Question 4 of 20 Since which release has every floating-point expression in Java been strict, the same on every platform?

    Choose one answer.

    Show the answer: Question 4 of 20

    Answer: JDK 17

    Java 1.0 was strict, Java SE 1.2 added a looser default with strictfp for strict code, and JDK 17 made every expression strict again, which is why javac now warns about strictfp.

  5. Question 5 of 20 Shortest.java prints 2e23 + " " + 1e23 + " " + 0.1. On JDK 17 the middle value came out longer than on JDK 25. What did JDK 17 print for 1e23?

    Choose one answer.

    Show the answer: Question 5 of 20

    Answer: 9.999999999999999E22

    The double nearest 10²³ is the same on both releases. JDK 17 printed it as 9.999999999999999E22 and JDK 25 as 1.0E23, the shortest decimal that names that double.

  6. Question 6 of 20 Is close(10_000_000_001.0, 10_000_000_000.0, 1e-9, 0) true or false?

    Choose one answer.

    Show the answer: Question 6 of 20

    Answer: True, because the allowance is 1e-9 × about 10¹⁰ = about 10, and the difference is 1

    relTol × max(|a|, |b|) = 1e-9 × 10,000,000,001 ≈ 10, and |a − b| = 1 is below it, so close returns true. A relative tolerance scales with the size of the numbers.

  7. Question 7 of 20 A test checks that a computed balance, which should be 0, is right. Which assertion fits?

    Choose one answer.

    Show the answer: Question 7 of 20

    Answer: close(balance, 0, 1e-9, 1e-9), with an absolute tolerance for the case near zero

    A relative tolerance alone fails next to zero. Add an absolute tolerance chosen from what the numbers mean, such as 1e-9 for amounts that need nine decimal places.

  8. Question 8 of 20 What does 1.0 / 0 give in Java?

    Choose one answer.

    Show the answer: Question 8 of 20

    Answer: Infinity

    1.0 / 0 is Infinity and -1.0 / 0 is -Infinity, while the integer division 1 / 0 throws ArithmeticException.

  9. Question 9 of 20 For double nan = 0.0 / 0.0;, which of these are true?

    Choose every answer that is right.

    Show the answer: Question 9 of 20

    Answer:

    • Double.isNaN(nan)
    • Double.compare(nan, nan) == 0
    • nan != nan

    Every comparison with NaN is false, so nan == nan and both halves of the last choice are false, and nan != nan is true. Double.isNaN is the test to use, and Double.compare treats NaN as equal to itself so that sorting works.

  10. Question 10 of 20 Which way does Math.round send a value exactly halfway between two whole numbers?

    Choose one answer.

    Show the answer: Question 10 of 20

    Answer: Toward positive infinity, so 2.5 becomes 3 and -2.5 becomes -2

    Math.round(2.5) is 3 and Math.round(-2.5) is -2, as the run printed. Math.rint(2.5) is 2.0, and a cast cuts instead of rounding.

  11. Question 11 of 20 What does String.format("%.2f", 2.675) return?

    Choose one answer.

    Show the answer: Question 11 of 20

    Answer: "2.68"

    Java's formatter rounds the digits Double.toString would print, "2.675", using the half-up rule, which gives "2.68", although the stored double is a little below 2.675. new BigDecimal(2.675) rounds the exact stored value and gives 2.67.

  12. Question 12 of 20 0.57 * 100 is 56.99999999999999. What is (int) (0.57 * 100)?

    Type a number.

    Show the answer: Question 12 of 20

    Answer: 56

    A cast from a floating-point type to an integer type drops the fractional part, so 56.99999999999999 becomes 56. Math.round(0.57 * 100) gives 57.

  13. Question 13 of 20 The double nearest 1.005 is 1.00499999999999989…. What does String.format("%.2f", 1.005) return?

    Choose one answer.

    Show the answer: Question 13 of 20

    Answer: "1.01"

    As with 2.675, %.2f rounds the digits Double.toString prints, "1.005", and gets 1.01, while the stored value itself is below 1.005. When the rounding has to follow a rule, round a BigDecimal built from the decimal instead.

  14. Question 14 of 20 a is new BigDecimal("1.50") and b is new BigDecimal("1.5"). What are a.equals(b) and a.compareTo(b)?

    Choose one answer.

    Show the answer: Question 14 of 20

    Answer: false and 0

    The values are equal, so compareTo returns 0. equals also compares the scale, 2 against 1, so it returns false. Use compareTo to compare amounts.

  15. Question 15 of 20 What happens when this runs?

    Read the code, then choose one answer.

    var total = new BigDecimal("10.00");
    IO.println(total.divide(BigDecimal.valueOf(3)));
    Show the answer: Question 15 of 20

    Answer: It throws ArithmeticException, because 10.00 / 3 has no finite decimal form

    divide without a scale must give the exact quotient, and 10/3 never ends in decimal. Say how many digits to keep and how to round: total.divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP) gives 3.33.

  16. Question 16 of 20 What is BigDecimal.valueOf(2.5).setScale(0, RoundingMode.HALF_EVEN)?

    Choose one answer.

    Show the answer: Question 16 of 20

    Answer: 2

    HALF_EVEN, banker's rounding, rounds 2.5 to 2 and 3.5 to 4, as Money.java's 2.345 became 2.34. HALF_UP would give 3.

  17. Question 17 of 20 A price arrives as the double 0.1. Which call gives a BigDecimal of exactly 0.1, scale 1?

    Choose one answer.

    Show the answer: Question 17 of 20

    Answer: BigDecimal.valueOf(0.1)

    new BigDecimal(double) keeps every binary digit of the double; BigDecimal.valueOf(double) starts from its shortest decimal form. Best of all is to keep money as text or whole cents and never let it pass through a double.

  18. Question 18 of 20 Why do the base and the tax, each rounded to two decimal places on its own, sometimes not add up to the price?

    Choose one answer.

    Show the answer: Question 18 of 20

    Answer: Each part is rounded on its own, so their two rounding errors can add up to a cent

    Rounding loses up to half a cent in each part, and two such losses, or gains, can make a whole cent. Round one part and make the other what is left.

  19. Question 19 of 20 The price 99.99 includes 18 % tax, and its base rounds to 84.74. What is the tax, made what is left of the price?

    Type a number.

    Show the answer: Question 19 of 20

    Answer: 15.25 (anything from 15.249 to 15.251 counts)

    99.99 − 84.74 = 15.25. That is one of the exercise's checks: base and tax always add up to the price when the tax is what is left.

  20. Question 20 of 20 A bill of 100.00 is shared by three people. Each share is bill.divide(three, 2, RoundingMode.DOWN), 33.33. What should the last person pay so that the shares add up to the bill?

    Choose one answer.

    Show the answer: Question 20 of 20

    Answer: 33.34, the bill minus the other two shares

    Money.java does exactly this: share, share and last = bill − 2 × share, printed as 33.33 + 33.33 + 33.34. Round every part but one, and let the last be what is left.

Interview questions

Fresher · Why should money not be stored in a double?

A double is a binary fraction, so most decimal amounts, such as 0.10 or 19.99, are stored as the nearest binary value rather than exactly, and each operation rounds again. Totals then miss by tiny amounts, such as 0.30000000000000004, comparisons with == fail, and rounding for display can give a different cent from the one an invoice rule expects. Use BigDecimal, built from strings, with an explicit scale and RoundingMode, or a long that counts the smallest unit, such as cents. Keep double for measurements, where a tiny relative error does not matter.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    Prices are BigDecimal values read from several suppliers. A deduplication step puts them in a HashSet and keeps both 19.9 and 19.90, while a TreeSet of the same prices keeps one. Explain, and say how you would store and compare prices.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A metrics job adds ten million readings of 0.1 with a double in a for loop and gets 999999.9998389754 instead of 1,000,000. Explain where the error comes from, how it grows, and how you would get the right total.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A double is an IEEE 754 binary64 number; most decimal fractions are stored as the nearest binary value, so 0.1 + 0.2 is 0.30000000000000004.
  • Since JDK 17 floating-point arithmetic gives identical results everywhere and strictfp does nothing; since JDK 19 Double.toString prints the shortest decimal that identifies the double.
  • Compare computed doubles with a tolerance, relative plus absolute near zero; test NaN with Double.isNaN, and remember that == and Double.compare disagree on NaN and on -0.0.
  • Math.round sends ties up, Math.rint to even; %.2f rounds the printed decimal digits half up; a cast to int cuts the fraction off.
  • Use BigDecimal for money: build it from strings, divide with a scale and a RoundingMode, compare with compareTo, because equals also compares the scale.
  • When a total is split, round one part and make the other what is left, so the parts always add up.

Terms in this lesson

IEEE 754
The standard for binary floating-point arithmetic that float and double follow: float is binary32 with 24 significant bits, double is binary64 with 53, and every operation rounds its exact result to the nearest representable value.
NaN
Not a Number: the double or float that 0.0 / 0.0 and Math.sqrt(-1) give. Every comparison with NaN is false, itself included, so it is found with Double.isNaN(x), never with ==.
rounding mode
The rule a BigDecimal follows when it must drop digits, given as a RoundingMode: HALF_UP sends a tie away from zero, HALF_EVEN to the even neighbour, DOWN cuts toward zero, and UNNECESSARY throws if any digit would be lost.
scale
The number of digits after the decimal point of a BigDecimal: 19.99 has scale 2. A BigDecimal is its unscaled whole number times 10 to the power minus its scale, so 2.0 and 2.00 have the same value but different scales.
ulp
Unit in the last place: the gap between a floating-point number and the next larger one of its type. Math.ulp(1.0) is 2.220446049250313E-16, and the gap grows with the size of the number.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.