-1

Edit: A possible solution: I tried to print remaining and I got this: 3.75 1.75 0.75 0.25 0.049999997 0.029999997 0.009999998

0.04 and 0.02 I guess are the issues!


My Qustion:

I Have to write a cash register program in java in-which I did the register have only the following notes:

  • One Franc: .01
  • Two Franc: .02
  • Five Franc: .05
  • Ten Franc: .10
  • Twenty Franc: .20
  • Fifty Franc: .50
  • One centime: 1
  • Two centimes: 2
  • Five centimes: 5
  • Ten centimes: 10
  • Twenty centimes: 20
  • Fifty centimes: 50

so for example:

input:

price = 11.25
cash = 20 

output:

Five Francs, Two Francs, One Franc, Fifty Centimes, Twenty Centimes, Five Centimes

my problem is that my code gives me this output instead:

Five Francs, Teo Francs, One Franc, Fifty Centimes, Twenty Centimes, Two Centimes, Two Centimes

Notice how instead of Five Centimes I get 2 of Two Centimes so I'm 1 Centime Short.


I solved it using a simple loop & Enum here it's:

My Enum:

public enum bill {
    Fifty_Francs( 50.00f),
   Twenty_Francs( 20.00f),
      Ten_Francs( 10.00f),
     Five_Francs(  5.00f),
      Teo_Francs(  2.00f),
       One_Franc(  1.00f),
     Fifty_Centimes(  0.50f),
    Twenty_Centimes(  0.20f),
       Ten_Centimes(  0.10f),
      Five_Centimes(  0.05f),
       Two_Centimes(  0.02f),
       One_Centime(  0.01f);
 
     private final float value;
     private final String description;
 
     bill(float value) {
         this.value = value;
         this.description = " " + this.name().replace("_", " ");
     }
 
     public float getValue() {
         return this.value;
     }
 
     @Override
     public String toString() {
         return this.description;
     }
 }

my printing function:


public static void getGhange(double price, double cash) {
    if (cash < price){
        System.out.println("Wrong buddy");
    } else if (cash == price) {
        System.out.println("Nothing");
    } else {  //CH > PP
        float remaining = (float) (cash - price);
        
        StringBuilder change = new StringBuilder();
        for (bill d : bill.values()) { 
            while (remaining >= d.getValue()) {
                remaining -= d.getValue();
                change.append(d).append(',');
            }
        }
        change.setLength(change.length() - 1); // remove , at the end
        System.out.println(change.toString().trim());
    }

}
ShifraSec
  • 106
  • 2
  • 9
  • 3
    I'm speculating but I guess it's a float issue right?? – ShifraSec Feb 09 '21 at 19:04
  • Check out the accepted answer to https://stackoverflow.com/questions/27598078/float-and-double-datatype-in-java It specifically mentions money and the comments have a good discussion on the subject. – RipeGorilla Feb 09 '21 at 22:51

4 Answers4

2

I suggest that you multiply everything by 100 and work only with integers.

This way you avoid float point imprecision on comparisons.

Alternatively you can use an arbitrary precision type.

Implementation of the first suggestion:

public class ShifraSec {
    public static void main(String[] args) {
        getGhange(11.25, 20);
    }
    public enum bill {
        Fifty_Francs( 5000),
        Twenty_Francs( 2000),
        Ten_Francs( 1000),
        Five_Francs(  500),
        Two_Francs(  200),
        One_Franc(  100),
        Fifty_Centimes(  50),
        Twenty_Centimes(  20),
        Ten_Centimes(  10),
        Five_Centimes(  5),
        Two_Centimes(  2),
        One_Centime(  1);

        private final float value;
        private final String description;

        bill(float value) {
            this.value = value;
            this.description = " " + this.name().replace("_", " ");
        }

        public float getValue() {
            return this.value;
        }

        @Override
        public String toString() {
            return this.description;
        }
    }

    public static void getGhange(double price, double cash) {
        int intPrice = (int)(price * 100);
        int intCash = (int)(cash * 100);
        if (intCash < intPrice){
            System.out.println("Wrong buddy");
        } else if (intCash == intPrice) {
            System.out.println("Nothing");
        } else {  //CH > PP
            int remaining = (intCash - intPrice);

            StringBuilder change = new StringBuilder();
            for (bill d : bill.values()) {
                while (remaining >= d.getValue()) {
                    remaining -= d.getValue();
                    change.append(d).append(',');
                }
            }
            change.setLength(change.length() - 1); // remove , at the end
            System.out.println(change.toString().trim());
        }

    }

}

Here is the output (i took the liberty of changing Teo Francs to Two Francs): ´Five Francs, Two Francs, One Franc, Fifty Centimes, Twenty Centimes, Five Centimes´

RipeGorilla
  • 70
  • 1
  • 5
  • I'm trying to use DecimalFormat but having problems with it< don't know exactly how to limit the decimal values only to 2, but still looking on the internet – ShifraSec Feb 09 '21 at 19:18
  • DecimalFormat is for formatting purposes, it won't fix the imprecision. Try using BigDecimal as @BoyFarmer suggested or work with integers (the lowest unit being 1 centime). – Eduardo Bissi Feb 09 '21 at 19:26
  • yeah, I tried using Formater didn't work thankx & upvoted for telling me why:) I thought it formates the actual data to my liking in-which python has something like that . – ShifraSec Feb 09 '21 at 19:30
1

When working with money, you should use dedicated library, or BigDecimal.

With BigDecimal you have to change your code:

public static void getGhange(BigDecimal price, BigDecimal cash) {
    if (cash.compareTo(price) < 0){
        System.out.println("Wrong buddy");
    } else if (cash.equals(price)) {
        System.out.println("Nothing");
    } else {  //CH > PP
        BigDecimal remaining = cash.subtract(price);

        StringBuilder change = new StringBuilder();
        for (bill d : bill.values()) {
            while (remaining.compareTo(d.getValue()) >= 0) {
                remaining = remaining.subtract(d.getValue(), new MathContext(2));
                change.append(d).append(',');
            }
        }
        change.setLength(change.length() - 1); // remove , at the end
        System.out.println(change.toString().trim());
    }

}

But now instead of Five Centimes it returns Two Centimes, Two Centimes, One Centime, so if you need Five Centimes you have to modify algorithm (but now it works with money as expected, without any problem with float precision)

Int his example precision is set to 2 digits remaining.subtract(d.getValue(), new MathContext(2));

and your enum method returnig value should return BigDecimal

public BigDecimal getValue() {
    return BigDecimal.valueOf(this.value);
}
BoyFarmer
  • 101
  • 4
  • Yeah I was considering BigDeciml as well but I don't understand how they work fully so I reverted to the easier method of using Math.round() "When working with money, you should use dedicated library" indeed upvote for that advice, that what you should do always. However, on my occasion I personally don't want to ... this is to refresh my mind about java cuz I didn't work with it for a long time hence why I want to work with core java only – ShifraSec Feb 09 '21 at 19:27
0

Ok, I was able to solve it found that using Math.round() inside the while loop solves it easily:

this line:

remaining -= d.getValue();

becomes this one:

remaining -= Math.round(d.getValue()*100) / 100.0

The great resource that I used: https://java2blog.com/java-round-double-float-to-2-decimal-places/

ShifraSec
  • 106
  • 2
  • 9
0

just make the below changes as also given above by a contributor.

public static void getChange(double price, double cash) {
        if (cash < price) {
            System.out.println("Wrong buddy");
        } else if (cash == price) {
            System.out.println("Nothing");
        } else { // CH > PP
            float remaining = (float) (cash - price);

            StringBuilder change = new StringBuilder();
            for (bill d : bill.values()) {
                while (remaining >= d.getValue()) {
                    remaining -= Math.round(d.getValue()*100.0)/100.0;
                    change.append(d).append(',');
                }
            }
            change.setLength(change.length() - 1); // remove , at the end
            System.out.println(change.toString().trim());
        }

    }

it will result as expected, still working with other approach which could make it more simple.

  • Replacing "while" by "if" will cause error where the bill must be duplicated. For example assume price = 11.24 and cash = 20. The last 2 bills should be "Two Centimes, Two Centimes" but using if will pass through Two Centimes only once. – Eduardo Bissi Feb 10 '21 at 12:23
  • Hi Eduardo thanx for your comment, i found the mistake. I have corrected my suggestion above. – ICanDebuggIt Feb 10 '21 at 13:02