3

When I call my function:

formatCurrency(7.5);

string formatCurrency(double cash) {
cout << "fmod(cash,.1) is equal to " << fmod(cash,.1) << endl;
if(fmod(cash,1) == 0) {
    cout << cash << ".00";
}
else if(fmod(cash,.1) == 0.1) {
    cout << cash << "0";
}
else if(fmod(cash,.01) == 0.01) {
    cout << cash;
}
else{
    cout << "Error: unable to display in currency format";
}
return "";
}

fmod(7.5,.1) is clearly equal to .1, and it even outputs as such when I run the program. But instead I get the following output:

fmod(cash,.1) is equal to 0.1
Error: unable to display in currency format

What gives? I see literally nothing wrong with my code. The code does work with whole numbers/the first if statement, but anything with a decimal makes things get dicey.

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • 3
    You are not comparing two floating-point values correctly. The correct way to compare them is to check if their difference is within some `delta`, where `delta` represents the margin of error you are willing to tolerate for your application. The reason for this is that most floating point values cannot be represented exactly in binary. – Mike Borkland Sep 01 '18 at 15:33
  • As far as I know variables and literals of floating-point types shouldn't be compared directly. – Ekzuzy Sep 01 '18 at 15:35
  • 2
    As @MikeBorkland said, and that's one reason you *never* use floating-point for cash. Instead, use integers. – Deduplicator Sep 01 '18 at 15:39
  • See also https://stackoverflow.com/a/5753519/2785528. Did you not see all the other floating point questions? float, double, long double all have the same issue ... a finite number of bits can not perfectly represent every value. This inaccuracy (finite resolution) is traded for convenience. – 2785528 Sep 01 '18 at 15:42
  • 1
    @MikeBorkland -- the correct way to compare floating point values for equality is to compare them for equality. Checking whether they are "nearly equal" introduces a host of other problems; it's not a no-brainer replacement. – Pete Becker Sep 01 '18 at 15:52
  • To see what's really going on, increase the precision of the output. That is, replace `cout << "fmod(cash,.1) is equal to " << fmod(cash,.1) << endl;` with `cout << set precision(16) << "fmod(cash,.1) is equal to " << fmod(cash,.1) << endl;`. The stream inserter in `cout << food(cash,.1)` rounds the result; the value is not actually `.1`. – Pete Becker Sep 01 '18 at 16:00
  • If you program serious cash managing software, use a **decimal** floating point library. Never use epsilon-based comparison for cash. – geza Sep 01 '18 at 16:05
  • 1
    note that just adding the epsilon is not enough, floating-point comparison is much more than that. Read [Comparing Floating Point Numbers, 2012 Edition](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/) and [What is the most effective way for float and double comparison?](https://stackoverflow.com/q/17333/995714) – phuclv Sep 02 '18 at 12:14

1 Answers1

3

Floating point data types are finite and suffer from imprecision. And the way they are encoded makes it very unintuitive for beginners as to for which values they are imprecise (e.g. 0.5 will be represented precisely, 0.1 needs to be rounded).

There are entire books written about the ins and outs of the IEEE floating point encodings, but for a beginner any Wikipedia article is a good start to get a feel of it: https://en.wikipedia.org/wiki/Floating-point_arithmetic

There are different solution for this problem, depending on your domain, e.g. using arbitrary precision libraries like MPFR (https://www.mpfr.org/). Most unit test frameworks settle for not using == equality, but instead introduce a "close enough" comparison. As an example, JUnit's assertEquals for double effectively checks the following:

Math.abs(d1 - d2) <= some_small_delta

see also: JUnit assertEquals(double expected, double actual, double epsilon)

So if the difference between the two numbers is small enough, they are considered equal. In the context of currency, one common alternative is to decide on the smallest denomination that your system needs (e.g. perhaps "cents" when the currency is "dollar") and always represent "1 dollar" as an int with value 100 instead of a floating point with value 1.0. There are even libraries to simplify this task, effectively modelling fixed point data types.

see also:

Pascal Kesseli
  • 1,620
  • 1
  • 21
  • 37