1

So I wrote this small practice program that tells you how many nickels and cents you would need for the amount given. I don't undersand why the program outputs 0 for cents even the the math is correct. I can't seam to understand what the issue is.

// Example program
#include <iostream>

using namespace std;

int main()
{
    const double nickels{0.05};
    const double cents{0.01};
    int amt_needed_nickels{0}, amt_needed_cents{0};
    double amt_val{0.06f}; // for 0.06 cents
    double amt_val_copy{amt_val};
    
    amt_needed_nickels = amt_val / nickels;
    if(amt_needed_nickels != 0)
        amt_val -= (nickels * amt_needed_nickels);
    amt_needed_cents =  amt_val / cents;
    
    cout << "If it costs $" << amt_val_copy << ", you'll need:\nNickels: " << amt_needed_nickels << "\nCents: " << amt_needed_cents << "\n";
    cout << "Even though amt_val is " << amt_val << ", and cents is also " << cents << ", and 0.01/0.01 does equal 1, why is amt_needed_cents not 1?\n";
}

To be fair I know if i change the const double cents{0.01} to const float cents{0.01}, and remove the f from double amt_val{0.06f}; to double amt_val{0.06}; it will work, but i fail to understand what's really going on beneath the surface. Why does the program give 0 to amt_needed_cents above, and 1 in the other cenario?

// Example program
#include <iostream>

using namespace std;

int main()
{
    const double nickels{0.05};
    const float cents{0.01};
    int amt_needed_nickels{0}, amt_needed_cents{0};
    double amt_val{0.06}; // for 0.06 cents
    double amt_val_copy{amt_val};
    
    amt_needed_nickels = amt_val / nickels;
    if(amt_needed_nickels != 0)
        amt_val -= (nickels * amt_needed_nickels);
    amt_needed_cents =  amt_val / cents;
    
    cout << "If it costs $" << amt_val_copy << ", you'll need:\nNickels: " << amt_needed_nickels << "\nCents: " << amt_needed_cents << "\n";
    cout << "I know this is correct, but I don't know what the compiler is thinking with this as compared to the other\n\n";
}


UniverseX
  • 21
  • 2
  • I think that you want this to be a float instead of a double: `double amt_val{0.06f}; // for 0.06 cents`. 0.0 is double by default. While 0.0f is a float. – bhristov Jul 10 '20 at 01:43
  • 1
    Don't use floating point for monetary calculations, as floating point is not exact. Neither `0.01`, `0.05` nor `0.06` have exact binary representations. Use integers, or if not, use another (third-party) data type that supports monetary calculations. – PaulMcKenzie Jul 10 '20 at 01:59
  • ya i think that's what i just figured so for example double var{0.0f} is the same as float var{0.0} ? But that still doesn't make me understand why float and double can result in different values but I guess it's just a type issue to be aware of..? – UniverseX Jul 10 '20 at 02:10
  • `0.0f` is explicitly a `float`. `0.0` is presumed to be a `double`. There's no reason for `f` unless you need an actual `float`. – tadman Jul 10 '20 at 02:41
  • Does this answer your question? [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – rsjaffe Jul 10 '20 at 03:33

2 Answers2

4

Floating point calculations are not precise, when doing math you get the very small difference, which will break your program if you try to check for an exact equality of the numbers. For example, 1.2 * 3 is not exactly 3.6 (note that this doesn't mean all calculations are inexact: e.g. multiplication by any power of 2 is always exact: things like 1.5 * 2 and 1.1 / 4 always give the best answer possible). This is because decimal fractions like 1/10 and 1/100 are not exactly representable in binary, which is an issue when our coins are based on 1/100 (the cent).

In your program, you should instead represent coins as an integers by converting all values to cents, then everything will work just fine. That is, you know that the largest denominator you will ever deal with is 100, so you may as well multiply everything by 100. So, nickels is going to be 5, cents is going to be 1 and all calculations will be precise.

To avoid any confusion, here's your code in integers:

int dollars = 100, nickels = 5, cents = 1;

int amt_needed_dollars = amt_val / dollars;
amt_val -= amt_needed_dollars * dollars;
int amt_needed_nickels = amt_val / nickels;
amt_val -= amt_needed_nickels * nickels;
// skipped dividing by cents, because it's '1' anyway
int amt_needed_cents = amt_val;
HTNW
  • 27,182
  • 1
  • 32
  • 60
lenik
  • 23,228
  • 4
  • 34
  • 43
  • 2
    More about floating point imprecision: [0.30000000000000004](https://0.30000000000000004.com/) – Jorengarenar Jul 10 '20 at 01:47
  • I just ran the code through a debugger and `amt_val` on line 16 came out to be `0.0099999986588954898` which dividing by `cents` yielded `0.99999986588954892`. When you turn the last quantity to an `int`, you get a zero. – unxnut Jul 10 '20 at 01:51
  • I did two versions one with whole numbers as cents and the second more precise using real numbers. I think i just figured that if you declared double var{0.0f} <-- the 0.0f seams to set var to a float even though it was declared as a double. I'm still not sure why that would be a problem though. But I guess it just type issues. – UniverseX Jul 10 '20 at 02:06
  • @unxnut check out `floor()` and `ceil()` -- these might be helpful in your case. – lenik Jul 10 '20 at 02:26
  • @UniverseX when you declare `0.f` -- it's zero, zero is precise, but if you try to declare other numbers in this way, most of them are going to be approximations. `double` gives better approximation, while `float` has less precision. For example, if you write `0.6`, it is going to be `0.59999999999` in the case of `double`, but only `0.5999990000000` in the case of `float` (the exact number of digits may differ, the numbers above are just for the illustration purpose. – lenik Jul 10 '20 at 02:30
  • Except `1.5 * 2` *is* exactly `3`. In binary, `1.5` is `1.1` and `2` is `10`. `float` has >20 bits after the binary point, and `double` has >50, so these two numbers *can* be exactly represented, and all IEEE operations are supposed to be exact too. `1.1 * 10 = 11` is decimal `3`, exactly. The issue is that coins are decimal fractions, and `10 = 2 * 5`, That `5` does not divide `2`, so decimal fractions are not exact in binary. Dyadic fractions like your `3/2` and the integers are exact (up to some limits). – HTNW Jul 10 '20 at 02:41
  • 1
    Re: "Floating point calculations are not precise" -- that's a seriously misleading (although quite common) oversimplification. Floating point calculations are precise; if you do the same calculation multiple times you'll get the same result every time. The problem that people have with floating-point math is that floating-point numbers are not real numbers, so most of what you've learned over the years about how real numbers work doesn't apply to floating-point numbers. – Pete Becker Jul 10 '20 at 12:18
-1
  • Float: A floating point number. It's a single-precision 32bit number. More common.
  • Double: Like the name it holds "double" the value of a float. It's a double-precision 64bit number. It's more precise and can hold way more decimal points.

I'm not the best with c++ but it looks like the problem is here:

int amt_needed_nickels{0}, amt_needed_cents{0};
amt_needed_nickels = amt_val / nickels;

Try changing amt_needed_cents to a float or double the parsing/rounding it later. Unlike other languages (like JS) c++ is annoying when trying to convert types like that. You are dividing two floats to an int which probably truncates the decimal.

Cyberboy1551
  • 345
  • 1
  • 13