2

With the following simple C++ exercise

#include <iostream>
using namespace std;
int main() 
{
    int euro, cents_v1, cents_v2, do_again;
    double price;

    do_again = 1;

    while(do_again != 0){

        cout<<"Insert price in Euro with cents"<<endl;
        cin>>price;

        euro = price;
        cents_v1 = price*100 - euro*100;
        cents_v2 = (price - euro) * 100;

        cout<<"Total: "<<euro<<" euro and "<<cents_v1<<" cents"<< endl;
        cout<<"Total: "<<euro<<" euro and "<<cents_v2<<" cents"<< endl;

        cout <<"\nDo it again? 1: yes, 0: no."<<endl;
        cin>>do_again;

    }
    return 0;

}

You can obtain two different answers, if the input is, for example 31.13:

Insert price in Euro with cents
31.13
Total: 31 euro and 13 cents
Total: 31 euro and 12 cents

Do it again? 1: yes, 0: no.

How to deal with this problem? Is there a rule in programming to avoid or to control this issue in more complicated situations?

200_success
  • 7,286
  • 1
  • 43
  • 74
SeF
  • 3,864
  • 2
  • 28
  • 41
  • 2
    The standard answer to this question [What every computer scientist should know about floating point](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – Martin York May 02 '16 at 14:10
  • Converting FP results to integers can suffer greatly due to truncation. Suggest rounding. `cents_v1 = round(price*100 - euro*100);` – chux - Reinstate Monica May 02 '16 at 22:10

2 Answers2

3

Floating-point addition and multiplication are commutative but not associative or distributive. Therefore, cents_v1 and cents_v2 may in fact represent different values, as you’ve observed.

One general technique to avoid these types of floating-point gotchas is to use an arbitrary-precision arithmetic library. There are quite a few of these, and I’m insufficiently familiar with them to recommend one over another. Using arbitrary-precision numbers incurs a performance penalty, of course, but until you know your arithmetic is a bottleneck, it’s unwise to optimize further.

If you absolutely must use floating-point arithmetic, there are a variety of rules of thumb to improve accuracy over long floating-point computations; look at some numerical methods texts for more information. There’s also a good body of research on accuracy in floating-point computation, which has produced some rather cool software.

Benjamin Barenblat
  • 1,311
  • 6
  • 19
1

Because of rounding issues you should never use float or double to represent monetary units (they are not exact and when it comes to money people want to be exact).

Try adding the following line to your code:

std::cout << std:: setprecision(17) << price << "\n";

With your input the result is:

31.129999999999999

So you either need to create a class to represent your money (not a bad idea). Or use an integer (as integers are always exact). So store the amount of money as the number of cents not the number of euros. Then only convert to euros and cents when displaying.

class DecimalCurrencyUnit
{
    long   cents;
    public:
        DecimalCurrencyUnit()
            : cents(0)
        {}
        DecimalCurrencyUnit(long euros, long cent)
            : cents(euros * 100 + cent)
        {}
        friend std::ostream& operator<<(std::ostream& s, DecimalCurrencyUnit const& out)
        {
            return s << '€'
                     << (out.cents / 100) << "." 
                     << std::setw(2) << std::setfill('0') << (out.cents % 100);
        }
        friend std::istream& operator>>(std::istream& s, DecimalCurrencyUnit& in)
        {
            long euros;
            long cent;
            char s;
            char x;
            if ((s >> s >> euros >> x >> cent) && s == '€' && x == '.' && cent < 100)
            {
                in.cents = euros * 100 + cent;
            }
            else
            {
                s.setsetate(std::ios::bad);
            }
            return s;
        }
        // Add standard operators here for *+/- etc.
}
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 1
    Maybe `long long`, to be sure that even my country's national debt can be represented... – Bob__ May 02 '16 at 18:42
  • @Bob__: If you want to do this in production then arbitrary precision library may be a better idea. If you are just messing around `long long` – Martin York May 02 '16 at 21:30