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 BasithUpdated 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.
- Explain why
0.1 + 0.2prints0.30000000000000004, and why adoubleis the wrong place to keep money. - Make
BigDecimalvalues from strings and calculate withadd,subtract,multiplyanddivide, keeping every result. - Read a
BigDecimal's scale and round it to cents withsetScaleand aRoundingMode. - Choose a rounding mode on purpose, including
HALF_EVEN, and divide without anArithmeticException. - Compare amounts with
compareTo, and say why2.0and2.00are notequals. - Use
BigIntegerfor whole numbers bigger than alongcan hold.
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.
public class Main { public static void main(String[] args) { double a = 0.1; double b = 0.2; double sum = a + b; System.out.println(sum); System.out.println(sum == 0.3); }}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.
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.
public class Main { public static void main(String[] args) { double total = 0.0; for (int i = 1; i <= 10; i++) { total = total + 0.10; // ten sweets at 10 cents each } System.out.println("Total: " + total); int cents = (int) (total * 100); // the card payment wants cents System.out.println("Charged: " + cents + " 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.
| Decimal | Built from | In binary | Exact in a double? |
|---|---|---|---|
0.5 | one half | 0.1 | Yes |
0.75 | a half and a quarter | 0.11 | Yes |
0.375 | a quarter and an eighth | 0.011 | Yes |
0.1 | no mix of halves, quarters, eighths and so on that ever ends | 0.000110011... | No |
0.2 | no mix that ever ends | 0.00110011... | No |
0.3 | no mix that ever ends | 0.0100110011... | No |
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.
- 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:
public class Main { public static void main(String[] args) { System.out.println(0.5 + 0.25 == 0.75); System.out.println(0.375 + 0.125 == 0.5); System.out.println(0.1 + 0.2 == 0.3); System.out.println(0.1 + 0.7); }}It prints true, true, false and 0.7999999999999999.
Key ideaA
doublestores 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.
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.
public class Main { public static void main(String[] args) { long total = 0; // in cents for (int i = 1; i <= 10; i++) { total = total + 10; // ten sweets at 10 cents each } System.out.println("Total: " + total + " cents"); long tax = total * 75 / 1000; // 7.5% tax System.out.println("Tax: " + tax + " 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.
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal price = new BigDecimal("0.10"); BigDecimal total = BigDecimal.ZERO; for (int i = 1; i <= 10; i++) { total = total.add(price); } System.out.println("Total: " + total); }}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.ZEROis a ready-made zero, a constant likeMath.PI. There are alsoBigDecimal.ONEandBigDecimal.TEN.total.add(price)adds with a method call. Java's+cannot add twoBigDecimalobjects, soBigDecimalhas 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:
import java.math.BigDecimal; public class Main { public static void main(String[] args) { System.out.println(new BigDecimal("0.1")); System.out.println(new BigDecimal(0.1)); System.out.println(BigDecimal.valueOf(0.1)); System.out.println(BigDecimal.valueOf(1999, 2)); System.out.println(BigDecimal.valueOf(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 adouble. Thedouble0.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 adouble, but first turns it into the text thatprintlnwould show,"0.1", and reads that. Use it when a value reaches you as adoubleand 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.
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.Methods instead of operators#
Each operator you would use on a double has a matching method:
a + ba.add(b)a - ba.subtract(b)a * ba.multiply(b)a / ba.divide(b, 2, RoundingMode.HALF_UP), explained in the section on dividing-aa.negate()Math.abs(a)a.abs()a < ba.compareTo(b) < 0, explained near the endHere is a basket: three items at 19.99, with a coupon for 5.00 off.
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal price = new BigDecimal("19.99"); BigDecimal quantity = BigDecimal.valueOf(3); BigDecimal coupon = new BigDecimal("5.00"); BigDecimal subtotal = price.multiply(quantity); BigDecimal toPay = subtotal.subtract(coupon); System.out.println("Subtotal: " + subtotal); System.out.println("To pay: " + toPay); System.out.println("Price is still " + price); }}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.
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal total = new BigDecimal("5.00"); BigDecimal delivery = new BigDecimal("2.50"); total.add(delivery); System.out.println(total); total = total.add(delivery); System.out.println(total); }}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 ideaMake amounts from strings, and keep the result of every calculation:
add,subtract,multiplyanddividereturn a newBigDecimaland 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 as | Unscaled value | Scale | Precision |
|---|---|---|---|
"19.99" | 1999 | 2 | 4 |
"2.50" | 250 | 2 | 3 |
"7" | 7 | 0 | 1 |
"0.05" | 5 | 2 | 1 |
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:
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal price = new BigDecimal("19.99"); System.out.println(price.unscaledValue()); System.out.println(price.scale()); System.out.println(price.precision()); BigDecimal fee = new BigDecimal("2.50"); System.out.println(fee.scale() + " " + fee.precision()); }}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:
import java.math.BigDecimal;import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal subtotal = new BigDecimal("59.97"); BigDecimal taxRate = new BigDecimal("0.08"); BigDecimal tax = subtotal.multiply(taxRate); System.out.println(tax + " has scale " + tax.scale()); BigDecimal roundedTax = tax.setScale(2, RoundingMode.HALF_UP); System.out.println(roundedTax + " has scale " + roundedTax.scale()); BigDecimal total = subtotal.add(roundedTax); System.out.println("Total: " + total); }}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:
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal price = new BigDecimal("2.5"); System.out.println(price.setScale(2)); BigDecimal tax = new BigDecimal("4.7976"); try { System.out.println(tax.setScale(2)); } catch (ArithmeticException e) { System.out.println(e); } }}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.
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:
| Mode | Rule | 2.5 to 0 places | -2.5 to 0 places | 2.45 to 1 place | 2.6 to 0 places |
|---|---|---|---|---|---|
UP | Away from zero | 3 | -3 | 2.5 | 3 |
DOWN | Toward zero: drop the digits | 2 | -2 | 2.4 | 2 |
CEILING | Toward positive infinity: the larger neighbour | 3 | -2 | 2.5 | 3 |
FLOOR | Toward negative infinity: the smaller neighbour | 2 | -3 | 2.4 | 2 |
HALF_UP | The nearer neighbour; a tie goes away from zero | 3 | -3 | 2.5 | 3 |
HALF_DOWN | The nearer neighbour; a tie goes toward zero | 2 | -2 | 2.4 | 3 |
HALF_EVEN | The nearer neighbour; a tie goes to the even digit | 2 | -2 | 2.4 | 3 |
UNNECESSARY | No rounding allowed | throws | throws | throws | throws |
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:
import java.math.BigDecimal;import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal a = new BigDecimal("2.5"); BigDecimal b = new BigDecimal("-2.5"); BigDecimal c = new BigDecimal("2.45"); for (RoundingMode mode : RoundingMode.values()) { if (mode == RoundingMode.UNNECESSARY) { continue; } System.out.println(mode + ": " + a.setScale(0, mode) + " " + b.setScale(0, mode) + " " + c.setScale(1, mode)); } }}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.
import java.math.BigDecimal;import java.math.RoundingMode; public class Main { public static void main(String[] args) { String[] amounts = {"0.5", "1.5", "2.5", "3.5"}; BigDecimal exact = BigDecimal.ZERO; BigDecimal halfUp = BigDecimal.ZERO; BigDecimal halfEven = BigDecimal.ZERO; for (String amount : amounts) { BigDecimal value = new BigDecimal(amount); exact = exact.add(value); halfUp = halfUp.add(value.setScale(0, RoundingMode.HALF_UP)); halfEven = halfEven.add(value.setScale(0, RoundingMode.HALF_EVEN)); } System.out.println("Exact: " + exact); System.out.println("HALF_UP: " + halfUp); System.out.println("HALF_EVEN: " + halfEven); }}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.
- UP2.5Always away from zero, however little is dropped.
- DOWN2.4Always toward zero: the extra digits are cut off.
- CEILING2.5Always toward positive infinity, so the larger neighbour.
- FLOOR2.4Always toward negative infinity, so the smaller neighbour.
- HALF_UP2.5Exactly halfway, and a tie goes away from zero.
- HALF_DOWN2.4Exactly halfway, and a tie goes toward zero.
- HALF_EVEN2.4Exactly halfway, and a tie goes to the neighbour whose last digit is even.
- UNNECESSARYthrowsA digit would be lost, so Java throws instead of picking a neighbour.java.lang.ArithmeticException: Rounding necessary
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.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:
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal bill = new BigDecimal("100.00"); System.out.println(bill.divide(BigDecimal.valueOf(4))); System.out.println(bill.divide(BigDecimal.valueOf(8))); try { System.out.println(bill.divide(BigDecimal.valueOf(3))); } catch (ArithmeticException e) { System.out.println(e); } }}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:
import java.math.BigDecimal;import java.math.RoundingMode; public class Main { public static void main(String[] args) { BigDecimal bill = new BigDecimal("100.00"); BigDecimal people = BigDecimal.valueOf(3); BigDecimal share = bill.divide(people, 2, RoundingMode.HALF_UP); System.out.println("Each pays " + share); System.out.println("Together they pay " + share.multiply(people)); }}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.
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.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:
import java.math.BigDecimal;import java.util.HashSet;import java.util.TreeSet; public class Main { public static void main(String[] args) { BigDecimal a = new BigDecimal("2.0"); BigDecimal b = new BigDecimal("2.00"); System.out.println(a.equals(b)); System.out.println(a.compareTo(b)); HashSet<BigDecimal> hashSet = new HashSet<>(); hashSet.add(a); hashSet.add(b); TreeSet<BigDecimal> treeSet = new TreeSet<>(); treeSet.add(a); treeSet.add(b); System.out.println(hashSet.size() + " " + treeSet.size()); }}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.
false.0, meaning equal.HashSet, HashMap and a list's contains.TreeSet, TreeMap and sorting.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:
import java.math.BigDecimal; public class Main { public static void main(String[] args) { BigDecimal owed = new BigDecimal("10.00"); BigDecimal paid = new BigDecimal("10.00"); BigDecimal balance = owed.subtract(paid); System.out.println(balance); System.out.println(balance.equals(BigDecimal.ZERO)); System.out.println(balance.compareTo(BigDecimal.ZERO) == 0); System.out.println(balance.signum()); BigDecimal wallet = new BigDecimal("20"); BigDecimal price = new BigDecimal("19.99"); System.out.println(wallet.compareTo(price) >= 0); }}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 ideaTo ask whether two amounts are the same number, write
a.compareTo(b) == 0. Put<,>,<=,>=or!=in place of==to ask the other questions.equalsalso checks the scale.
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.== 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.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.
import java.math.BigDecimal;import java.math.RoundingMode; public class Main { public static void main(String[] args) { double sum = 0.1 + 0.2; System.out.printf("%.2f%n", sum); System.out.println(sum); BigDecimal total = new BigDecimal("64.7652"); System.out.printf("Total: %.2f%n", total); System.out.println("Total: " + total.setScale(2, RoundingMode.HALF_UP)); System.out.println("Total: " + total.setScale(2, RoundingMode.DOWN)); String line = String.format("%s x %d = %.2f", new BigDecimal("4.99"), 3, new BigDecimal("14.97")); System.out.println(line); }}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.
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.
import java.math.BigInteger; public class Main { public static void main(String[] args) { long biggest = Long.MAX_VALUE; System.out.println(biggest); System.out.println(biggest + 1); BigInteger big = BigInteger.valueOf(Long.MAX_VALUE); System.out.println(big.add(BigInteger.ONE)); }}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.
import java.math.BigInteger; public class Main { public static void main(String[] args) { long smallFactorial = 1; BigInteger bigFactorial = BigInteger.ONE; for (int i = 1; i <= 25; i++) { smallFactorial = smallFactorial * i; bigFactorial = bigFactorial.multiply(BigInteger.valueOf(i)); } System.out.println("25! as a long: " + smallFactorial); System.out.println("25! as a BigInteger: " + bigFactorial); System.out.println("2 to the 100th: " + BigInteger.TWO.pow(100)); }}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.
18446744073709551615. Which type holds that number exactly?Try it
Answer each one in your head or on paper first, then open the answer.
0.5 + 0.25 == 0.75 print true while 0.1 + 0.2 == 0.3 prints false?BigDecimal price = new BigDecimal(19.99);. What would you change, and why?new BigDecimal("3.10")?Practice
Work through these on your own. Each one builds on this page.
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.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.BigInteger to work out 50!. How many digits does it have, and how many zeros does it end with? Explain the zeros.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#
- java.math.BigDecimal in the Java SE 25 API documentation, with the rules for scale, rounding, equals and compareTo
- java.math.RoundingMode in the Java SE 25 API documentation, with a table of every mode on ten sample numbers
- java.math.BigInteger in the Java SE 25 API documentation
- Floating-point types and values, section 4.2.3 of the Java Language Specification, on how float and double follow the IEEE 754 standard
Spotted a mistake, or a part that did not make sense? Report it by email