Java Arena
Step 7 of 7

Money and rounding errors

Doubles aren't exact

Try adding a few small decimal numbers:

System.out.println(0.1 + 0.2);
double total = 0.0;
for (int i = 0; i < 10; i++) {
    total = total + 0.1;
}
System.out.println(total);
System.out.println(total == 1.0);
Output
0.30000000000000004
0.9999999999999999
false

A double stores its number in binary, with a fixed number of binary digits. Most decimal fractions, 0.1 among them, can't be written exactly that way, just as 1/3 can't be written exactly as a decimal number (0.3333...). So a double holds the closest number it can, which is off by a tiny amount. Usually the difference is too small to notice, but it can show after a single sum, as in 0.1 + 0.2, and the errors add up as more values are summed, as in the loop. And == can say false for two doubles that should be equal.

For a temperature or a distance, errors that small don't matter. For money they do: a balance should never be a fraction of a cent off.

Money in whole cents

The simple fix is to count money in its smallest unit, cents, and to keep it in an int: 2.50 euros is 250 cents. Adding and subtracting whole numbers is exact, as long as the result fits in an int: its largest value is 2147483647, so up to about 21 million euros in cents. (Java also has a class for exact decimal numbers, BigDecimal, but whole cents in an int are all this course needs.)

To show cents as euros, divide by 100 for the euros, and take what's left with % for the cents. The cents always need two digits: 205 cents is 2.05, not 2.5, so cents below 10 get a 0 in front. This print quota for a library printer keeps its balance that way:

Main.java
public class Main {
    public static void main(String[] args) {
        PrintQuota quota = new PrintQuota(500);
        System.out.println(quota);
        quota.printPages(7);
        System.out.println(quota);
        boolean printed = quota.printPages(30);
        System.out.println(printed);
        System.out.println(quota);
    }
}
PrintQuota.java
public class PrintQuota {
    private int cents;

    public PrintQuota(int cents) {
        this.cents = cents;
    }

    public boolean printPages(int pages) {
        int cost = pages * 15;
        if (cost > this.cents) {
            return false;
        }
        this.cents = this.cents - cost;
        return true;
    }

    public String toString() {
        int euros = this.cents / 100;
        int rest = this.cents % 100;
        if (rest < 10) {
            return euros + ".0" + rest + " euros";
        }
        return euros + "." + rest + " euros";
    }
}
Output
5.00 euros
3.95 euros
false
3.95 euros
  • The balance is in cents, in an int. A page costs 15 cents, so 7 pages cost exactly 105 cents.
  • printPages has a rule: it prints only when the balance covers the cost, so the balance never goes below zero. It returns true or false, so the caller knows whether it worked. 30 pages would cost 450 cents, more than the 395 left.
  • Only toString shows the money as euros. Every calculation stays in whole cents.

Your task

Challenge 1 of 3

A CoinJar keeps its money in cents. Add a toString that shows the money as euros and cents, with two digits for the cents and the word euros after them. The check adds a 2-euro coin (200 cents) and a 5-cent coin, and prints the jar:

2.05 euros

Challenge 2

A parking meter takes only coins of 10, 20, 50, 100 and 200 cents, and every 10 cents buys 3 minutes. Write insert(int coin), which adds a valid coin to the money paid and returns true, but returns false for any other value and leaves the money as it was. Also write minutes(), which returns the minutes the money paid buys. toString is ready, and it calls minutes(). The check inserts 100, 5 and 20 cents, printing what insert returns each time, and then prints the meter:

true
false
true
120 cents, 36 minutes

Challenge 3

A VendingMachine keeps the money put in as credit, in cents. Write two methods. buy(int price) takes the price off the credit and returns true when the credit covers the price; otherwise it returns false, and the credit stays as it was. change() returns the credit that's left and sets the credit to 0, as when the machine gives the coins back. The check puts in 200 cents, tries to buy two things that cost 150 cents, and asks for change twice:

true
false
50
0