Hi, I'm writing a simple program that runs a vending machine. I thought I had everything working fine untill I messed around with the money input some more. In my program I obviously compare money values and check to make sure the values match up exactly to buy an item. It works under almost every case, but I have found some cases where I get an anomalous decimal value that throws off the comparison of the double values. Here is my code and output from testing within the money class itself.

public class Money {
    private static final double NICKEL_AMMNT = 0.05;
    private static final double DIME_AMMNT = 0.10;
    private static final double QRT_AMMNT = 0.25;


    private int numNickels, numDimes, numQrts;



    public Money(int numNickels, int numDimes, int numQrts) {
        this.numNickels = numNickels;

        this.numDimes = numDimes ;

        this.numQrts = numQrts ;

    }

    public int getNickels(){
        return numNickels;
    }

    public int getDimes(){
        return numDimes;
    }

    public int getQrts(){
        return numQrts;
    }

    public double getTotal(){
        double total = ((numNickels * NICKEL_AMMNT) + (numDimes * DIME_AMMNT) + (numQrts * QRT_AMMNT));

        return total;
        }

    public String toString(){
        String tempString = "";
        tempString += ("Nickels: "+getNickels()+"\n");
        tempString += ("Dimes: "+getDimes()+"\n");
        tempString += ("Quarters: "+getQrts()+"\n");
        tempString += ("Total Cash: $"+(getNickels()*NICKEL_AMMNT + getDimes()*DIME_AMMNT + getQrts()*QRT_AMMNT));
        return tempString;
    }

    public static void main(String[] args){
        Money money1 = new Money(2, 3, 1);
        System.out.println("2 Nickels, 3 Dimes, 1 Quarter");
        System.out.println(money1.getTotal()+"\n");

        Money money2 = new Money(3, 2, 3);
        System.out.println("3 Nickels, 2 Dimes, 3 Quarters");
        System.out.println(money2.getTotal()+"\n");

        Money money3 = new Money(1,6,0);

        System.out.println("1 Nickels, 6 Dimes, 0 Quarters\n"+money3.getTotal());

        Money money4 = new Money(3,0,0);
        System.out.println("\n3 Nickels,0 Dimes, 0 Quarters\n"
                +money4.getTotal());
    }

}

The output from this code in main gives me:
2 Nickels, 3 Dimes, 1 Quarter
0.65

3 Nickels, 2 Dimes, 3 Quarters
1.1

1 Nickels, 6 Dimes, 0 Quarters
0.6500000000000001

3 Nickels,0 Dimes, 0 Quarters
0.15000000000000002

It works fine under most cases, but I'm sometimes getting weird results when adding up the values. Can someone explain first why this is occuring, and how to fix it, or foce my double values to be two decimal places?

Dani AI

Generated

Binary floating-point is the cause of the strange outputs (0.6500000000000001, 0.15000000000000002). Decimal fractions like 0.05 and 0.10 have no exact binary representation, so rounding artifacts appear when using double. 's advice to work in whole cents is the right, pragmatic choice for a vending-machine problem; 's change to integer coin values already fixes the core error.

A practical pattern that avoids any floating-point equality problems:

  • keep all money internally as whole cents (use int or long);
  • do all arithmetic and comparisons on those integers;
  • format to dollars/cents only when displaying.

Example (formatting/display only):

int totalCents = numNickels*5 + numDimes*10 + numQrts*25;
int dollars = totalCents / 100;
int cents = Math.abs(totalCents % 100);
String out = String.format("$%d.%02d", dollars, cents);

For comparisons, compare integers:

if (totalCents >= priceCents) { /* exact, safe check */ }

If the application later needs fractional currency (interest, taxes, split bills), prefer BigDecimal for arithmetic and use compareTo() rather than equals(). When constructing BigDecimal, avoid new BigDecimal(double); use BigDecimal.valueOf(long, int) or new BigDecimal(String) and set an explicit RoundingMode when required.

A final note: returning a double from a helper like getTotal() can reintroduce display artifacts; either return the integer cents or return a formatted string. For UI/printing, NumberFormat.getCurrencyInstance() or the integer-based String.format above will produce stable two-decimal output.

Recommended Answers

All 3 Replies

It's a mistake to use doubles here. Decimal fractions (eg 0.1) can't be exactly represented in a binary floating point format, so rounding errors are inevitable.
Your values are always an exact integer number of cents, so the obvious simplest (correct) way to hold the values in cents, not dollars, and store them in int variables. That way your calculations will always be exactly correct.
You may find it usefulto write a simple little method that takes a valaue in cents and converts it to a display string in $ddd.cc format, so your printouts are easy and look good.

Thanks, I'll try that and see where it gets me

Here is the fix which I tested and it seems to work fine. Thanks for the tip.

public class Money {
        //Nickels,Dimes,Qrts stored as number of cents in integers (i.e. NICKEL_AMMNT = 5 is 5 cents)
        //values are calculated as dollar amounts in getTotal() method
    private static final int NICKEL_AMMNT = 5;
    private static final int DIME_AMMNT = 10;
    private static final int QRT_AMMNT = 25;

    //the number of nickels,dimes,qrts that the user inputs
    private int numNickels, numDimes, numQrts;


    public Money(){
        numNickels = 0;
        numDimes = 0;
        numQrts = 0;
    }

    public Money(int numNickels, int numDimes, int numQrts) {
        this.numNickels = numNickels;

        this.numDimes = numDimes ;

        this.numQrts = numQrts ;

    }

    public int getNickels(){
        return numNickels;
    }

    public int getDimes(){
        return numDimes;
    }

    public int getQrts(){
        return numQrts;
    }

    public double getTotal(){
        double nickels = (double)(numNickels * NICKEL_AMMNT)/100;
        double dimes = (double)(numDimes * DIME_AMMNT)/100;
        double qrts = (double)(numQrts * QRT_AMMNT)/100;
        double total = (nickels+dimes+qrts);
        return total;

    }

    public String toString(){
        String tempString = "";
        tempString += ("Nickels: "+getNickels()+"\n");
        tempString += ("Dimes: "+getDimes()+"\n");
        tempString += ("Quarters: "+getQrts()+"\n");
        tempString += ("Total Cash: $"+getTotal());
        return tempString;
    }

    public static void main(String[] args){
        Money money1 = new Money(2, 3, 6);
        System.out.println(money1.toString()+"\n"); // toString implements get nickels,dimes,qrts, and getTotal()


        Money money2 = new Money(0,1,1);
        System.out.println(money2.toString()+"\n");


        Money money3 = new Money(7,0,0);
        System.out.println(money3.toString()+"\n");


        Money money4 = new Money(1,6,0);
        System.out.println(money4.toString());




    }

}

With the testing output:
Nickels: 3
Dimes: 3
Quarters: 6
Total Cash: $1.95

Nickels: 0
Dimes: 1
Quarters: 1
Total Cash: $0.35

Nickels: 7
Dimes: 0
Quarters: 0
Total Cash: $0.35

Nickels: 1
Dimes: 6
Quarters: 0
Total Cash: $0.65

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.