-2

Hey so I'm trying to learn Java, and I know about floating-points not being accurate and what not, but I'm feeling blind at the moment. I know I'm doing something wrong, the computer is not wrong, but I don't know where.

I'm casting the Math returns as integers and then subtracting the specific integers value from the variable cash; when I subtract integers from floats, the float seems to lose data. My question is as follows: What is the smartest way to avoid data spoilage without slowing down my program?

<!-- language: lang-java -->
private static void calculateChange()
{
    System.out.print("Please enter any amount of cash in dollars: ");
    float cash = scanner.nextFloat();

    int ten_dollars = (int) Math.floor(cash/10);
    cash -= 10 * ten_dollars;

    System.out.println(cash);

    int five_dollars = (int) Math.floor(cash/5);
    cash -= 5 * five_dollars;
    int dollars = (int) Math.floor(cash);
    cash -= dollars;
    int quarters = (int) Math.floor(cash/0.25);
    cash -= 0.25 * quarters;
    int dimes = (int) Math.floor(cash/0.1);
    cash -= 0.1 * dimes;
    int nickels = (int) Math.floor(cash/0.05);
    cash -= 0.05 * nickels;
    int pennies = (int) Math.floor(cash/0.01);

    System.out.println("This is equal to:\n" + ten_dollars + " ten dollar bills\n"
            + five_dollars + " five dollar bills\n"
            + dollars + " one dollar bills\n"
            + quarters + " quarters\n"
            + dimes + " dimes\n"
            + nickels + " nickels\n"
            + pennies + " pennies");
}

run:

Please enter any amount of cash in dollars: 135,04

5.0399933

This is equal to:

13 ten dollar bills

1 five dollar bills

0 one dollar bills

0 quarters

0 dimes

0 nickels

3 pennies

BUILD SUCCESSFUL (total time: 6 seconds)

B. Lee
  • 191
  • 6
  • 9
    Don't use float to store currency, that's it. They are not suitable for that purpose. – Jack Jul 07 '14 at 00:27
  • 2
    "floating-points not being accurate" Floating-point *is* accurate to the specs of IEEE 754. They just aren't "accurate" the way we might expect them to be. As stated by Jack and countless others: **don't use floating-point variables to represent monetary values**. Use `int`/`long` (which would probably make your program *faster* once you got past the parsing part), or if you really need to, use `BigDecimal` (which is going to be slower and more awkward than floating-point) – awksp Jul 07 '14 at 00:29
  • 2
    And the second thing you need to understand is that most of your *constants,* e.g. 0.01, can't be represented exactly in floating point. So the program isn't exact even before execution: the compiled constants are inexact. You need to use the module operator. – user207421 Jul 07 '14 at 00:30
  • Err, modulus operation, instead of where you multiply. – user207421 Jul 07 '14 at 00:36
  • I tried multiplying all my values by a hundred and changes my variable 'cash' from float to int to see if it works. Yet it gets the wrong value. If I'm using all ints and casting the Math returns as integers, why am I getting the wrong amount of pennies? – B. Lee Jul 07 '14 at 00:38
  • If you're using all `int` values, you shouldn't need to do any casting at all... Are you still dividing by floating-point values, perhaps? Using all `int`s should work if floating-point numbers never enter into the equation – awksp Jul 07 '14 at 00:40
  • Yeah, I used the Math.floor method earlier which demanded a double, but I now removed it as java automatically rounds down all integers with decimals. But even then, I get the same output as the one I wrote in my post. – B. Lee Jul 07 '14 at 00:46
  • Are you still multiplying by `0.25`, etc. for cents? – awksp Jul 07 '14 at 00:49
  • Nope, it's all above the decimal. The problem now is: scanner.nextFloat() * 100, I suppose I could store that in a temporary float... or something. Edit: No, I guess I can't. How do I store accurate user input of a number such as '130.04'? BigDecimal? Or is there any other solution? – B. Lee Jul 07 '14 at 00:53
  • int dollars = 130 int cents = 4 just as an example – takendarkk Jul 07 '14 at 01:00
  • Okay so I solved it, but I'd like some help if anyone is willing to help. My solution was not that difficult, but I feel that it might be a MacGyver solution. I stored the user input data in a string, and then removed the decimal comma through the method String.replace(). I then parsed the string into an int. So please tell me if there's any easier solution to this silly problem. – B. Lee Jul 07 '14 at 01:00
  • BigDecimal. You were told that in the second comment. – user207421 Jul 07 '14 at 01:10
  • @XMLParsing You say you tried multiplying your values by 100. This is the best approach. But to get the first integer, if you're using `nextFloat`, you will need to multiply the `float` by 100 and then use `Math.round()`, not `Math.floor()` or a simple cast, because your 135.04 input will actually be 135.0399933 and if you use `floor` or a cast after multiplying by 100, you will get 13503 instead of 13504. – ajb Jul 07 '14 at 01:10
  • Thank you @ajb. I feel dumb for not seeing `Math.round()` as the most viable option here. – B. Lee Jul 07 '14 at 01:19

2 Answers2

1

You could use BigDecimal like so,

// Pass the amount in
private static void calculateChange(BigDecimal cash) {
  BigDecimal TEN = new BigDecimal(10);
  BigDecimal FIVE = new BigDecimal(5);
  BigDecimal QUARTER = new BigDecimal("0.25");
  BigDecimal TENTH = new BigDecimal("0.1");
  BigDecimal FIVE100ths = new BigDecimal("0.05");

  int ten_dollars = cash.divide(TEN).intValue();
  cash = cash.subtract(TEN.multiply(new BigDecimal(ten_dollars)));

  System.out.println(cash);

  int five_dollars = cash.divide(FIVE).intValue();
  cash = cash.subtract(FIVE.multiply(new BigDecimal(five_dollars)));
  int dollars = cash.divide(BigDecimal.ONE).intValue();
  cash = cash.subtract(new BigDecimal(dollars));
  int quarters = cash.divide(QUARTER).intValue();
  cash = cash.subtract(QUARTER.multiply(new BigDecimal(quarters)));
  int dimes = cash.divide(TENTH).intValue();
  cash = cash.subtract(TENTH.multiply(new BigDecimal(dimes)));
  int nickels = cash.divide(FIVE100ths).intValue();
  cash = cash.subtract(FIVE100ths.multiply(new BigDecimal(nickels)));
  int pennies = cash.multiply(new BigDecimal(100)).intValue();

  System.out.println("This is equal to:\n"
      + ten_dollars + " ten dollar bills\n"
      + five_dollars + " five dollar bills\n"
      + dollars + " one dollar bills\n" + quarters
      + " quarters\n" + dimes + " dimes\n" + nickels
      + " nickels\n" + pennies + " pennies");
}
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • Thank you for taking your time helping me, but I was trying to avoid BigDecimal as they have a significant impact on performance. But I definitely learned a thing or two about BigDecimals from your code example. Thank you. – B. Lee Jul 07 '14 at 01:18
  • @XMLParsing They have an impact on performance it is true, but *[premature optimization](http://c2.com/cgi/wiki?PrematureOptimization) is the root of all evil* - Knuth. – Elliott Frisch Jul 07 '14 at 01:24
  • 1
    @ElliottFrisch: Why have I only ever seen that quote pulled out when the poster is not, in fact, committing "premature optimisation"? – tmyklebu Jul 07 '14 at 01:28
  • @tmyklebu How much more performant is using primitives here? An order of magnitude, two orders of magnitude? How many calculations are we performing `20` or `25`? How many MIPS does your phone have? Also, there's a scanner in use, so we have to account for IO wait. How much of the run-time is spent there? – Elliott Frisch Jul 07 '14 at 01:31
  • @ElliottFrisch: Performance isn't the issue. Using BigDecimal in Java code is butt-ugly and no fun to read. You also have to horse around with rounding modes and whatever else. Primitives let you write your operations in infix for ready comprehension. – tmyklebu Jul 07 '14 at 01:37
  • If you're accepting input from a user, performance is completely irrelevant. You will spend far longer waiting for the user than you could ever save by avoiding BigDecimal. – user207421 Jul 07 '14 at 01:44
  • @tmyklebu Fine, but OP said *I was trying to avoid BigDecimal as they have a significant impact on performance.* – Elliott Frisch Jul 07 '14 at 02:11
1

I think you were on the right track to multiply everything by 100 and represent your dollar amount as an int that represents the number of pennies. However, if you start by inputting a floating-point value with getFloat, you will need to round the value after multiplying by 100, using Math.round.

This works:

System.out.print("Please enter any amount of cash in dollars: ");
float cash = scanner.nextFloat();
int cashInPennies = Math.round(cash*100);
System.out.println(cashInPennies);
// If you say cashInPennies = (int)(cash*100), the result will be 13503 instead of
// 13504.

int ten_dollars = cashInPennies/1000;
cashInPennies %= 1000;

System.out.println(cashInPennies);

int five_dollars = cashInPennies/500;
cashInPennies %= 500;
int dollars = cashInPennies/100;
cashInPennies %= 100;
int quarters = cashInPennies/25;
cashInPennies %= 25;
int dimes = cashInPennies/10;
cashInPennies %= 10;
int nickels = cashInPennies/5;
cashInPennies %= 5;
int pennies = cashInPennies;
System.out.println("This is equal to:\n" + ten_dollars + " ten dollar bills\n"
        + five_dollars + " five dollar bills\n"
        + dollars + " one dollar bills\n"
        + quarters + " quarters\n"
        + dimes + " dimes\n"
        + nickels + " nickels\n"
        + pennies + " pennies");
ajb
  • 31,309
  • 3
  • 58
  • 84