Learn Java

Lesson 51Phase 5: Errors, files and the standard library

Money and exact numbers: BigDecimal and BigInteger

Why 0.1 + 0.2 is not 0.3 in a double, how BigDecimal keeps money exact, and BigInteger for numbers too big for a long.

By Updated 45 min

  • floating point
  • BigDecimal
  • scale
  • RoundingMode
  • setScale
  • compareTo and equals

Ten sweets at 10 cents each cost 1.00. Add them up in Java with a double and you get 0.9999999999999999. Turn that into cents for the card payment and the shop charges 99 cents. This lesson shows where the cent goes, and how BigDecimal keeps money exact to the last digit. It ends with BigInteger, for whole numbers too big even for a long.

By the end of this lesson
  • Explain why 0.1 + 0.2 prints 0.30000000000000004, and why a double is the wrong place to keep money.
  • Make BigDecimal values from strings and calculate with add, subtract, multiply and divide, keeping every result.
  • Read a BigDecimal's scale and round it to cents with setScale and a RoundingMode.
  • Choose a rounding mode on purpose, including HALF_EVEN, and divide without an ArithmeticException.
  • Compare amounts with compareTo, and say why 2.0 and 2.00 are not equals.
  • Use BigInteger for whole numbers bigger than a long can hold.
From before
You have kept decimals in a double since variables and types, and operators and casting showed that (int) chops off a fraction. Wrapper classes showed Long.MAX_VALUE and what happens past it. Strings introduced objects that never change, and exception handling gave you try and catch. Near the end you need equals and hashCode from equals, hashCode and toString, and compareTo and TreeSet from Comparable, Comparator and sorting.

When 0.1 + 0.2 is not 0.3#

Start with the smallest program that shows the problem. It adds two prices, 0.1 and 0.2, and checks whether the sum is 0.3. Press Run and step through it.

Bug.java
1public class Main {
2 public static void main(String[] args) {
3 double a = 0.1;
4 double b = 0.2;
5 double sum = a + b;
6
7 System.out.println(sum);
8 System.out.println(sum == 0.3);
9 }
10}
Prints
›0.30000000000000004
›false

The first line prints 0.30000000000000004 and the second prints false. The trouble starts before the adding, when 0.1 and 0.2 are stored: a double cannot hold either of them exactly, so the sum comes out a tiny bit off, and == compares the stored values exactly.

Coming from Python or JavaScript?
You get the same 0.30000000000000004 there, because Python and JavaScript keep decimals in the same kind of number as a Java double. Python's fix is its decimal module, which works like the BigDecimalclass in this lesson. Python's whole numbers already grow as big as they need to, which is what BigInteger adds to Java, and JavaScript has BigInt for the same job.

A cent goes missing#

One wrong digit far after the point looks harmless. Here it is in a shop. Ten sweets cost 10 cents each, and the program adds them to a total. Payment systems often want the amount as a whole number of cents, so the last lines convert the total. Step through it and watch total in the variables view.

Sweets.java
1public class Main {
2 public static void main(String[] args) {
3 double total = 0.0;
4 for (int i = 1; i <= 10; i++) {
5 total = total + 0.10; // ten sweets at 10 cents each
6 }
7 System.out.println("Total: " + total);
8
9 int cents = (int) (total * 100); // the card payment wants cents
10 System.out.println("Charged: " + cents + " cents");
11 }
12}
Prints
›Total: 0.9999999999999999
›Charged: 99 cents

The third sweet already makes the total 0.30000000000000004. After the fourth it looks right again, 0.4, but from the eighth sweet on it stays just below the right answer and ends at 0.9999999999999999. Times 100 that is 99.99999999999999, and the (int) cast chops off everything after the point. The program prints Total: 0.9999999999999999 and then Charged: 99 cents: one cent short on a sale of 1.00.

Why a double cannot hold 0.1#

Think about how you write numbers. Each place after the decimal point is worth a tenth of the place before it: tenths, then hundredths, then thousandths. Some fractions fit in a few places, such as a quarter, 0.25. Others never end. A third is 0.3333... with the 3s going on forever, so wherever you stop writing, the number is a little short of a third.

A double stores numbers in binary, counting in twos instead of tens. Each place after the point is worth half the place before it: halves, then quarters, then eighths, then sixteenths. A fraction built from those fits exactly. One tenth is not one of them. In binary it is 0.000110011001100110011..., with the pattern 0011 repeating forever, the same way the 3s repeat in a third.

DecimalBuilt fromIn binaryExact in a double?
0.5one half0.1Yes
0.75a half and a quarter0.11Yes
0.375a quarter and an eighth0.011Yes
0.1no mix of halves, quarters, eighths and so on that ever ends0.000110011...No
0.2no mix that ever ends0.00110011...No
0.3no mix that ever ends0.0100110011...No
In the binary column each place is worth half the one before, so 0.1 in binary means one half and 0.01 means one quarter.

A double keeps 53 binary digits, about 16 significant decimal digits. It cuts the endless pattern off and stores the nearest number it can. For 0.1 that is 0.1000000000000000055511151231257827021181583404541015625, a tiny bit more than one tenth.

So why does System.out.println(0.1) show 0.1? When Java prints a double, it prints the shortest decimal that leads back to the same stored value, and for this one that is 0.1. The error is still there, only hidden. Adding the stored 0.1 and the stored 0.2 lands on the double just above the one that 0.3 is stored as, and the shortest decimal for that one is 0.30000000000000004.

Type a decimal below to see the exact value its double holds, or try 0.1 + 0.2.

Inside a doublenew BigDecimal(double)
Try
System.out.println(d) prints
0.1
new BigDecimal(d), the value d really holds
0.1000000000000000055511151231257827021181583404541015625

The double is a tiny bit above 0.1, by 0.0000000000000000055511151231257827021181583404541015625.

Numbers built from halves and quarters behave perfectly. The others pick up tiny errors:

Fractions.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println(0.5 + 0.25 == 0.75);
4 System.out.println(0.375 + 0.125 == 0.5);
5 System.out.println(0.1 + 0.2 == 0.3);
6 System.out.println(0.1 + 0.7);
7 }
8}
Prints
›true
›true
›false
›0.7999999999999999

It prints true, true, false and 0.7999999999999999.

Key idea

A double stores the nearest binary fraction to your number. Halves, quarters and eighths fit exactly. Tenths and hundredths do not, so most prices are stored a tiny bit wrong, and the errors add up as you calculate.

Doubles are fine for measurements
A distance or a temperature is approximate to begin with, and an error in the 17th digit is far smaller than any ruler or thermometer can notice. Keep using double for those. Money is different: an amount has to be right to the cent, every time, and people do check.

A first fix: count whole cents#

One way out is to avoid fractions altogether. Store every amount as a whole number of cents in a long: 10 cents is 10 and 19.99 is 1999. Whole numbers are exact, so adding and subtracting never drift. A tax of 7.5% is 75 thousandths, so this program multiplies by 75 and divides by 1000.

Cents.java
1public class Main {
2 public static void main(String[] args) {
3 long total = 0; // in cents
4 for (int i = 1; i <= 10; i++) {
5 total = total + 10; // ten sweets at 10 cents each
6 }
7 System.out.println("Total: " + total + " cents");
8
9 long tax = total * 75 / 1000; // 7.5% tax
10 System.out.println("Tax: " + tax + " cents");
11 }
12}
Prints
›Total: 100 cents
›Tax: 7 cents

The total is exactly 100 cents. The tax line shows the weak spot: 7.5% of 100 cents is 7.5 cents, and whole-number division keeps only the whole part, so the program prints Tax: 7 cents and the half cent disappears without a word. With whole cents, every percentage or split needs a rounding rule written by hand. Java has a class that keeps decimals exact and makes you name the rounding rule whenever one is needed: BigDecimal.

BigDecimal: decimals that stay exact#

BigDecimal lives in the package java.math, so a file that uses it starts with import java.math.BigDecimal;. A BigDecimal stores a decimal number digit for digit, in base ten, with as many digits as it needs. Here are the ten sweets again.

SweetsExact.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal price = new BigDecimal("0.10");
6 BigDecimal total = BigDecimal.ZERO;
7
8 for (int i = 1; i <= 10; i++) {
9 total = total.add(price);
10 }
11 System.out.println("Total: " + total);
12 }
13}
Prints
›Total: 1.00

It prints Total: 1.00, and the narration spells out every step: 0.10 + 0.10 is exactly 0.20, and so on up to 1.00. Three things are new.

  • new BigDecimal("0.10") builds the price from text. The text holds exactly the digits you typed, so the price is exactly 0.10.
  • BigDecimal.ZERO is a ready-made zero, a constant like Math.PI. There are also BigDecimal.ONE and BigDecimal.TEN.
  • total.add(price) adds with a method call. Java's + cannot add two BigDecimal objects, so BigDecimal has a method for each kind of arithmetic.

The total keeps two decimal places, 1.00, because the price was written with two. The section on scale explains the rule.

Make it from a String#

There are several ways to make a BigDecimal, and one of them brings the double problem straight back. Compare them:

Making.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 System.out.println(new BigDecimal("0.1"));
6 System.out.println(new BigDecimal(0.1));
7 System.out.println(BigDecimal.valueOf(0.1));
8 System.out.println(BigDecimal.valueOf(1999, 2));
9 System.out.println(BigDecimal.valueOf(3));
10 }
11}
Prints
›0.1
›0.1000000000000000055511151231257827021181583404541015625
›0.1
›19.99
›3

It prints 0.1, then 0.1000000000000000055511151231257827021181583404541015625, then 0.1, 19.99 and 3. Line by line:

  • new BigDecimal("0.1") reads the digits from text, so it holds exactly 0.1. Use it for amounts you write in your code or read from a file or a form.
  • new BigDecimal(0.1) takes a double. The double 0.1 is already a little more than 0.1 by the time the constructor sees it, and this constructor keeps every digit of the stored value.
  • BigDecimal.valueOf(0.1) also takes a double, but first turns it into the text that println would show, "0.1", and reads that. Use it when a value reaches you as a double and you cannot change that.
  • BigDecimal.valueOf(1999, 2) means 1999 with the point moved 2 places to the left: 19.99. It suits amounts kept as whole cents.
  • BigDecimal.valueOf(3) makes a whole number, handy for quantities.
Never hand a double to new BigDecimal
The documentation of BigDecimal itself calls the doubleconstructor “somewhat unpredictable”, for exactly the reason you just saw. Write amounts as strings, such as new BigDecimal("19.99"). The text may hold a sign, digits and one decimal point (and an exponent such as E3, which you will rarely need). Text such as "12,50" or "$5" throws a NumberFormatException.
Quick check
Three of these hold exactly the value 0.1. Which one does not?

Methods instead of operators#

Each operator you would use on a double has a matching method:

With double
With BigDecimal
a + b
a.add(b)
a - b
a.subtract(b)
a * b
a.multiply(b)
a / b
a.divide(b, 2, RoundingMode.HALF_UP), explained in the section on dividing
-a
a.negate()
Math.abs(a)
a.abs()
a < b
a.compareTo(b) < 0, explained near the end

Here is a basket: three items at 19.99, with a coupon for 5.00 off.

Basket.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal price = new BigDecimal("19.99");
6 BigDecimal quantity = BigDecimal.valueOf(3);
7 BigDecimal coupon = new BigDecimal("5.00");
8
9 BigDecimal subtotal = price.multiply(quantity);
10 BigDecimal toPay = subtotal.subtract(coupon);
11
12 System.out.println("Subtotal: " + subtotal);
13 System.out.println("To pay: " + toPay);
14 System.out.println("Price is still " + price);
15 }
16}
Prints
›Subtotal: 59.97
›To pay: 54.97
›Price is still 19.99

It prints Subtotal: 59.97, To pay: 54.97 and Price is still 19.99. Joining a BigDecimal to a string with + works as for any object: Java calls its toString. The last line shows that calling multiply on price left price as it was.

A BigDecimal never changes#

Like a String, a BigDecimal is immutable: once made, it never changes. Each method hands back a new BigDecimal holding the answer. Decide what this program prints before you run it.

Predict the outputDelivery.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal total = new BigDecimal("5.00");
6 BigDecimal delivery = new BigDecimal("2.50");
7
8 total.add(delivery);
9 System.out.println(total);
10
11 total = total.add(delivery);
12 System.out.println(total);
13 }
14}

What do the two lines print?

It prints 5.00 and then 7.50. The line total.add(delivery); works out 7.50 and throws the answer away, because nothing stores it. Only total = total.add(delivery); keeps it. The compiler accepts the first form without a word (some editors warn about it), which makes it a common BigDecimal bug.

Key idea

Make amounts from strings, and keep the result of every calculation: add, subtract, multiply and divide return a new BigDecimal and leave the one you called them on alone.

Scale: how many digits after the point#

Inside, a BigDecimal is two whole numbers. The unscaled value is all of its digits written as one whole number, and the scale says how many of those digits come after the decimal point. So 19.99 is the unscaled value 1999 with scale 2. The precision is how many digits the unscaled value has.

Written asUnscaled valueScalePrecision
"19.99"199924
"2.50"25023
"7"701
"0.05"521
The value is the unscaled value with the decimal point moved scale places to the left.

Scale is why "2.50" keeps its last zero: the text had two decimal places, so the number does too. On a price tag that is what you want. Each part has a method:

Inside.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal price = new BigDecimal("19.99");
6 System.out.println(price.unscaledValue());
7 System.out.println(price.scale());
8 System.out.println(price.precision());
9
10 BigDecimal fee = new BigDecimal("2.50");
11 System.out.println(fee.scale() + " " + fee.precision());
12 }
13}
Prints
›1999
›2
›4
›2 3

It prints 1999, 2 and 4 for the price, then 2 3 for the fee.

The scale of a result#

Adding or subtracting gives the larger of the two scales, so 19.99 plus 5 is 24.99. Multiplying adds the two scales together, because hundredths times hundredths are ten-thousandths. That is how a tax calculation grows extra places. Step through this one:

Tax.java
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3
4public class Main {
5 public static void main(String[] args) {
6 BigDecimal subtotal = new BigDecimal("59.97");
7 BigDecimal taxRate = new BigDecimal("0.08");
8
9 BigDecimal tax = subtotal.multiply(taxRate);
10 System.out.println(tax + " has scale " + tax.scale());
11
12 BigDecimal roundedTax = tax.setScale(2, RoundingMode.HALF_UP);
13 System.out.println(roundedTax + " has scale " + roundedTax.scale());
14
15 BigDecimal total = subtotal.add(roundedTax);
16 System.out.println("Total: " + total);
17 }
18}
Prints
›4.7976 has scale 4
›4.80 has scale 2
›Total: 64.77

59.97 has scale 2 and the rate 0.08 has scale 2, so the tax comes out with scale 4: 4.7976 has scale 4. Nobody can pay 4.7976, so the next line rounds it to cents, and the program prints 4.80 has scale 2 and Total: 64.77.

setScale: rounding to a number of places#

tax.setScale(2, RoundingMode.HALF_UP) returns a new BigDecimal with 2 decimal places, rounded by the rule HALF_UP: the school rule, which rounds up when the first digit you drop is 5 or more. The next section covers the other rules. RoundingMode is an enum in java.math, so it needs its own import. The name setScale sounds like it changes the object, but the BigDecimal you call it on keeps its old scale. Store the result.

Leave the rounding mode out and setScale does only the jobs that need no rounding. Adding zeros is fine. Dropping a digit that is not zero throws an ArithmeticException:

SetScale.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal price = new BigDecimal("2.5");
6 System.out.println(price.setScale(2));
7
8 BigDecimal tax = new BigDecimal("4.7976");
9 try {
10 System.out.println(tax.setScale(2));
11 } catch (ArithmeticException e) {
12 System.out.println(e);
13 }
14 }
15}
Prints
›2.50
›java.lang.ArithmeticException: Rounding necessary

It prints 2.50, then java.lang.ArithmeticException: Rounding necessary. Printing the exception e shows its full class name and its message, the same text you would see if the exception crashed the program.

Round when money changes hands
Keep every digit while a calculation runs, and round when an amount becomes real, such as a line on a receipt or a payment. Each rounding in the middle of a calculation adds a small error of its own.

Rounding modes#

Rounding 2.45 to one decimal place means choosing one of its two neighbours, 2.4 or 2.5, and 2.45 sits exactly halfway between them. The school rule sends a tie up, but that is only one reasonable rule among several. Java's RoundingMode enum names eight. Here is what each one does to a few numbers:

ModeRule2.5 to 0 places-2.5 to 0 places2.45 to 1 place2.6 to 0 places
UPAway from zero3-32.53
DOWNToward zero: drop the digits2-22.42
CEILINGToward positive infinity: the larger neighbour3-22.53
FLOORToward negative infinity: the smaller neighbour2-32.42
HALF_UPThe nearer neighbour; a tie goes away from zero3-32.53
HALF_DOWNThe nearer neighbour; a tie goes toward zero2-22.43
HALF_EVENThe nearer neighbour; a tie goes to the even digit2-22.43
UNNECESSARYNo rounding allowedthrowsthrowsthrowsthrows
A tie means the dropped part is exactly half a step, as in 2.5 and 2.45. 2.6 is past halfway, so the three HALF modes agree on it.

The modes come in groups. UP and DOWN think about distance from zero, so they treat -2.5 like 2.5 with a minus sign. CEILING and FLOOR think about the number line, where up means toward the bigger number, so CEILING turns -2.5 into -2. The three HALF modes pick the nearer neighbour and only disagree on a tie. UNNECESSARY is a promise that no rounding is needed: if a digit would be lost, it throws an ArithmeticException instead.

This loop works out the first three number columns of the table in Java. RoundingMode.values() lists the modes in order, as it does for any enum:

RoundingModes.java
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3
4public class Main {
5 public static void main(String[] args) {
6 BigDecimal a = new BigDecimal("2.5");
7 BigDecimal b = new BigDecimal("-2.5");
8 BigDecimal c = new BigDecimal("2.45");
9
10 for (RoundingMode mode : RoundingMode.values()) {
11 if (mode == RoundingMode.UNNECESSARY) {
12 continue;
13 }
14 System.out.println(mode + ": " + a.setScale(0, mode) + " "
15 + b.setScale(0, mode) + " " + c.setScale(1, mode));
16 }
17 }
18}
Prints
›UP: 3 -3 2.5
›DOWN: 2 -2 2.4
›CEILING: 3 -2 2.5
›FLOOR: 2 -3 2.4
›HALF_UP: 3 -3 2.5
›HALF_DOWN: 2 -2 2.4
›HALF_EVEN: 2 -2 2.4

It prints one line per mode, matching the table: UP gives 3, -3 and 2.5, and so on down to HALF_EVEN, which gives 2, -2 and 2.4. The continue skips UNNECESSARY, which would throw on all three numbers.

HALF_EVEN, or banker's rounding#

HALF_EVEN breaks a tie by going to the neighbour whose last digit is even: 2.5 becomes 2, 3.5 becomes 4, and 2.45 becomes 2.4. That looks odd until you round many numbers and add them up. HALF_UP sends every tie upward, so over thousands of roundings the total drifts up. HALF_EVEN sends about half of the ties up and half down, so the drift cancels out.

BankersRounding.java
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3
4public class Main {
5 public static void main(String[] args) {
6 String[] amounts = {"0.5", "1.5", "2.5", "3.5"};
7 BigDecimal exact = BigDecimal.ZERO;
8 BigDecimal halfUp = BigDecimal.ZERO;
9 BigDecimal halfEven = BigDecimal.ZERO;
10
11 for (String amount : amounts) {
12 BigDecimal value = new BigDecimal(amount);
13 exact = exact.add(value);
14 halfUp = halfUp.add(value.setScale(0, RoundingMode.HALF_UP));
15 halfEven = halfEven.add(value.setScale(0, RoundingMode.HALF_EVEN));
16 }
17 System.out.println("Exact: " + exact);
18 System.out.println("HALF_UP: " + halfUp);
19 System.out.println("HALF_EVEN: " + halfEven);
20 }
21}
Prints
›Exact: 8.0
›HALF_UP: 10
›HALF_EVEN: 8

The four amounts add up to 8.0. Rounded with HALF_UP they become 1, 2, 3 and 4, which add up to 10. Rounded with HALF_EVEN they become 0, 2, 2 and 4, which add up to 8, the true total.

The documentation of RoundingMode notes that HALF_EVEN is sometimes called banker's rounding, and that, on average, it keeps the total error smallest when you round over and over. Most double arithmetic in Java rounds the same way when it fits a result into the nearest value a double can store.

Try your own numbers. Type a decimal, choose how many places to keep, and read each mode's answer with the reason for it. The two dots beside an answer show whether it went to the smaller or the larger neighbour on the number line.

Rounding mode explorersetScale(places, mode)
Places to keep1
Try
new BigDecimal("2.45").setScale(1, mode) // mode: each row below
halfway2.42.52.45
  • UP2.5
    Always away from zero, however little is dropped.
  • DOWN2.4
    Always toward zero: the extra digits are cut off.
  • CEILING2.5
    Always toward positive infinity, so the larger neighbour.
  • FLOOR2.4
    Always toward negative infinity, so the smaller neighbour.
  • HALF_UP2.5
    Exactly halfway, and a tie goes away from zero.
  • HALF_DOWN2.4
    Exactly halfway, and a tie goes toward zero.
  • HALF_EVEN2.4
    Exactly halfway, and a tie goes to the neighbour whose last digit is even.
  • UNNECESSARYthrows
    A digit would be lost, so Java throws instead of picking a neighbour.java.lang.ArithmeticException: Rounding necessary
Which mode should money use?
Use the rule your situation sets, since a tax law or a contract often names one. Without such a rule, HALF_UP gives what most customers expect on a receipt, and HALF_EVEN suits work where many rounded amounts get added together, such as interest on thousands of accounts.
Quick check
Which rounding mode turns 2.5 into 2 and 3.5 into 4?

Dividing: when the digits never end#

Adding, subtracting and multiplying two exact decimals always gives an exact decimal with a known number of places. Division does not. 100 divided by 4 is 25, but 100 divided by 3 is 33.333... with 3s forever, the same kind of problem the double had with a tenth. BigDecimal refuses to guess where to stop:

Divide.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal bill = new BigDecimal("100.00");
6
7 System.out.println(bill.divide(BigDecimal.valueOf(4)));
8 System.out.println(bill.divide(BigDecimal.valueOf(8)));
9 try {
10 System.out.println(bill.divide(BigDecimal.valueOf(3)));
11 } catch (ArithmeticException e) {
12 System.out.println(e);
13 }
14 }
15}
Prints
›25.00
›12.50
›java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

Dividing by 4 prints 25.00 and dividing by 8 prints 12.50, since both answers end. Dividing by 3 throws java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. In plain words: the digits of the answer go on forever, so no BigDecimal can hold it exactly.

The fix is to say how many places you want and how to round. bill.divide(people, 2, RoundingMode.HALF_UP) gives the answer to 2 decimal places, rounded with HALF_UP:

SplitBill.java
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3
4public class Main {
5 public static void main(String[] args) {
6 BigDecimal bill = new BigDecimal("100.00");
7 BigDecimal people = BigDecimal.valueOf(3);
8
9 BigDecimal share = bill.divide(people, 2, RoundingMode.HALF_UP);
10 System.out.println("Each pays " + share);
11 System.out.println("Together they pay " + share.multiply(people));
12 }
13}
Prints
›Each pays 33.33
›Together they pay 99.99

Each of the three friends pays 33.33, and together they pay 99.99. The bill was 100.00, so one cent is left over. No rounding mode can cut 100.00 into three equal shares of whole cents: someone has to pay a cent more than the others.

Give every money division a scale and a rounding mode
divide with one argument only works when the answer happens to end. Code that splits money between people or months should always pass a number of places and a RoundingMode. Dividing by zero throws an ArithmeticException whichever form you use.
ExerciseFairSplit.java
Make the split fair to the cent. Each share is the bill divided by the number of people, rounded down to whole cents with RoundingMode.DOWN. What that leaves over is the bill minus the share times the number of people, and the first person pays it on top of their share. Fill in the two TODO lines so that 100.00 split three ways prints 33.34 and 33.33, and 10.00 split four ways prints 2.50 twice.

equals and compareTo#

Is 2.0 the same amount as 2.00? As numbers, yes. As BigDecimal objects they differ: 2.0 is the unscaled value 20 with scale 1, and 2.00 is 200 with scale 2. Java gives you a method for each view. compareTo compares the values and returns 0 when they are equal. equals compares the values and the scales. Predict what this prints:

Predict the outputTwoSets.java
1import java.math.BigDecimal;
2import java.util.HashSet;
3import java.util.TreeSet;
4
5public class Main {
6 public static void main(String[] args) {
7 BigDecimal a = new BigDecimal("2.0");
8 BigDecimal b = new BigDecimal("2.00");
9 System.out.println(a.equals(b));
10 System.out.println(a.compareTo(b));
11
12 HashSet<BigDecimal> hashSet = new HashSet<>();
13 hashSet.add(a);
14 hashSet.add(b);
15 TreeSet<BigDecimal> treeSet = new TreeSet<>();
16 treeSet.add(a);
17 treeSet.add(b);
18 System.out.println(hashSet.size() + " " + treeSet.size());
19 }
20}

What do the three lines print?

It prints false, 0 and 2 1. A HashSet decides what counts as a duplicate with hashCode and equals, as you saw in equals, hashCode and toString. The two scales give two different hash codes and equals says false, so the set keeps both. A TreeSet sorts with compareTo instead, which calls the two equal, so it keeps only the first one, 2.0.

The documentation calls this an ordering that is inconsistent with equals, and warns you to take care when BigDecimal values go into a sorted set or become the keys of a sorted map.

equals
compareTo
Compares the value and the scale.
Compares the value only.
2.0 and 2.00 give false.
2.0 and 2.00 give 0, meaning equal.
Used by HashSet, HashMap and a list's contains.
Used by TreeSet, TreeMap and sorting.
Asks: are these the same number, written the same way?
Asks: is this amount smaller, the same or bigger?

Most questions about money are value questions. Here is a customer's account after they pay what they owe, and a wallet checked against a price:

Settled.java
1import java.math.BigDecimal;
2
3public class Main {
4 public static void main(String[] args) {
5 BigDecimal owed = new BigDecimal("10.00");
6 BigDecimal paid = new BigDecimal("10.00");
7 BigDecimal balance = owed.subtract(paid);
8
9 System.out.println(balance);
10 System.out.println(balance.equals(BigDecimal.ZERO));
11 System.out.println(balance.compareTo(BigDecimal.ZERO) == 0);
12 System.out.println(balance.signum());
13
14 BigDecimal wallet = new BigDecimal("20");
15 BigDecimal price = new BigDecimal("19.99");
16 System.out.println(wallet.compareTo(price) >= 0);
17 }
18}
Prints
›0.00
›false
›true
›0
›true

The balance prints as 0.00. balance.equals(BigDecimal.ZERO) is false, because BigDecimal.ZERO has scale 0 and the balance has scale 2, while balance.compareTo(BigDecimal.ZERO) == 0 is true. signum() returns -1, 0 or 1 for a negative number, zero or a positive number, which makes it a short way to ask about zero. The last line prints true: a wallet of 20 covers a price of 19.99, and compareTo(...) >= 0 stands in for >=.

Key idea

To ask whether two amounts are the same number, write a.compareTo(b) == 0. Put <, >, <=, >= or != in place of == to ask the other questions. equals also checks the scale.

One scale for every amount
If amounts go into a HashSet or become HashMap keys, give them all the same scale first, for example with setScale(2). Then equals and compareTo agree. stripTrailingZeros() is another way to make 2.0 and 2.00 alike, but its results can print in surprising forms: new BigDecimal("100").stripTrailingZeros() prints 1E+2.
Never compare BigDecimals with ==
== asks whether two variables point at the same object, as with Integer in wrapper classes. Two BigDecimal objects that hold the same amount are usually separate objects, so == says false.
Quick check
After a customer pays, balance is new BigDecimal("10.00").subtract(new BigDecimal("10.00")). Which test correctly says the account is settled?

Printing money#

System.out.printf prints text with blanks in it, filled in from the values that follow. Its first argument is a format string. Inside it, %.2f is a blank for a number shown with exactly 2 decimal places, %d a blank for a whole number, %s a blank for any value as text, and %n ends the line. String.format takes the same arguments and returns the text instead of printing it.

Receipt.java
1import java.math.BigDecimal;
2import java.math.RoundingMode;
3
4public class Main {
5 public static void main(String[] args) {
6 double sum = 0.1 + 0.2;
7 System.out.printf("%.2f%n", sum);
8 System.out.println(sum);
9
10 BigDecimal total = new BigDecimal("64.7652");
11 System.out.printf("Total: %.2f%n", total);
12 System.out.println("Total: " + total.setScale(2, RoundingMode.HALF_UP));
13 System.out.println("Total: " + total.setScale(2, RoundingMode.DOWN));
14
15 String line = String.format("%s x %d = %.2f", new BigDecimal("4.99"), 3, new BigDecimal("14.97"));
16 System.out.println(line);
17 }
18}
Prints
›0.30
›0.30000000000000004
›Total: 64.77
›Total: 64.77
›Total: 64.76
›4.99 x 3 = 14.97

It prints 0.30, 0.30000000000000004, then Total: 64.77 twice and Total: 64.76, and last 4.99 x 3 = 14.97.

Look at the first two lines. printf rounded only the text it printed, and the variable sum still holds 0.30000000000000004. %.2f always rounds with HALF_UP. setScale does the same job with the rounding mode of your choice, and it gives you a value you can keep calculating with. Its toString prints exactly that many places.

Put the lines in orderInvoice.java
Put the lines in order to print a small invoice: three items at 4.99, a 7% tax rounded to cents, and the total. The right order prints Subtotal: 14.97, Tax: 1.05 and Total: 16.02.

Tap a line, then tap where it should go. Or use the arrows.

BigInteger: whole numbers with no ceiling#

A long holds whole numbers up to 9223372036854775807. As wrapper classes showed, going past the top wraps around to the most negative value, and Java does not warn you. BigInteger, also in java.math, is a whole number with no fixed limit: it grows to as many digits as the answer needs.

Overflow.java
1import java.math.BigInteger;
2
3public class Main {
4 public static void main(String[] args) {
5 long biggest = Long.MAX_VALUE;
6 System.out.println(biggest);
7 System.out.println(biggest + 1);
8
9 BigInteger big = BigInteger.valueOf(Long.MAX_VALUE);
10 System.out.println(big.add(BigInteger.ONE));
11 }
12}
Prints
›9223372036854775807
›-9223372036854775808
›9223372036854775808

The long prints 9223372036854775807, then wraps to -9223372036854775808 when you add 1. The BigInteger carries on to 9223372036854775808. BigInteger.valueOf turns a long into a BigInteger, and BigInteger.ONE is a ready-made 1, next to ZERO, TWO and TEN.

Factorials grow fast. 25 factorial, written 25!, is 1 × 2 × 3 × ... × 25. This program works it out twice, once in a long and once in a BigInteger. Step through it and watch smallFactorial when i reaches 21: 21! is bigger than any long, and the value turns negative.

Factorial.java
1import java.math.BigInteger;
2
3public class Main {
4 public static void main(String[] args) {
5 long smallFactorial = 1;
6 BigInteger bigFactorial = BigInteger.ONE;
7
8 for (int i = 1; i <= 25; i++) {
9 smallFactorial = smallFactorial * i;
10 bigFactorial = bigFactorial.multiply(BigInteger.valueOf(i));
11 }
12 System.out.println("25! as a long: " + smallFactorial);
13 System.out.println("25! as a BigInteger: " + bigFactorial);
14 System.out.println("2 to the 100th: " + BigInteger.TWO.pow(100));
15 }
16}
Prints
›25! as a long: 7034535277573963776
›25! as a BigInteger: 15511210043330985984000000
›2 to the 100th: 1267650600228229401496703205376

The long ends at 7034535277573963776, a wrong answer that looks believable. The BigInteger gives the real 25!, 15511210043330985984000000. The last line raises 2 to the 100th power with pow: 1267650600228229401496703205376.

BigInteger works like BigDecimal. It never changes, and you calculate with add, subtract, multiply, divide, mod and pow. Its divide drops the remainder, the way / does on two int values. It has no scale, so its equals and compareTo always agree. To make one from a number too long to write as a long, pass the digits as text: new BigInteger("123456789012345678901234567890"). Programs use it for exact answers to counting problems, and in cryptography, where keys are numbers hundreds of digits long.

Quick check
Put one grain of rice on the first square of a chessboard, two on the second, four on the third, and keep doubling up to the 64th square. The board then holds 2 to the 64th minus 1 grains: 18446744073709551615. Which type holds that number exactly?

Try it

Answer each one in your head or on paper first, then open the answer.

1
Why does 0.5 + 0.25 == 0.75 print true while 0.1 + 0.2 == 0.3 prints false?
2
A teammate writes BigDecimal price = new BigDecimal(19.99);. What would you change, and why?
3
What are the unscaled value, the scale and the precision of new BigDecimal("3.10")?
4
What does this program print, and why do the two lines differ?
5
Two pairs of rounding modes always agree on positive numbers but disagree on negative ones. Which pairs, and why?
6
Why does dividing 1 by 8 work without a rounding mode, while dividing 1 by 3 throws?

Practice

Work through these on your own. Each one builds on this page.

1
A savings account starts at 1000.00 and earns 3% a year. Write a loop that adds ten years of interest, rounding each year's interest to cents with HALF_EVEN, and prints the balance after each year. Then write the same loop with double and compare the last lines of the two versions.
2
Write static BigDecimal parsePrice(String text) that turns text such as "4.5" or "12" into a price with exactly two decimal places (4.50, 12.00). A price with more than two decimal places, such as "1.999", should be refused with a message, and so should text that is not a number.
3
Use BigInteger to work out 50!. How many digits does it have, and how many zeros does it end with? Explain the zeros.
4
Make a HashMap<BigDecimal, String> of price labels and put in new BigDecimal("4.5") with the label “small”. Then look up new BigDecimal("4.50") and print what get returns. Explain the result, then fix the program so that every key goes in, and every lookup happens, with scale 2.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email