3

I'm writing a Roulette-like C++ command-line program. The user may input decimal values/numbers for betting. I'm using double-type variables to make this possible. However, if I for example start with 1 dollar, then 0.23 dollars and lose, then bet 0.55 dollars and lose, and then bet 0.07 dollars and lose again, I can't bet 0.15 dollars, even though the program claims I actually have 0.15 dollars (you can't bet more money than you have). It appears the program is incorrectly substracting. However, I still can bet 0.149 dollars. For what it's worth, I use stringstream to convert the user's betting input to a double-type value. Can somebody explain what is going on here?

Here is my code:

#include <iostream>
#include <sstream>
using namespace std; //Std namespace.

void string_to_number(string input, double& destination);

class Roulette {
private:
int randoms;
double money, choice, bet;
string input;
public:
int play = 0;
void start_amount() {
    cout<<"How much money do you have?: ";
    getline(cin, input);
    string_to_number(input, money);
}

void betting() {
   cout<<"How much money would you like to bet?: ";
    getline(cin, input);
    string_to_number(input, bet);

    while (bet > money) {
        cout<<"You can't bet more money than you have ("<<money<<" dollars). Please enter again: ";
        getline(cin, input);
        string_to_number(input, bet);
    }
}

void choose_number() {
    cout<<"Which number do you choose? (0-35): ";
    getline(cin, input);
    string_to_number(input, choice);
}

void random_number() {
    cout<<"The wheel is spinning..."<<endl<<flush;
    randoms = (rand())%36;
}

void scenarios() {
    cout<<"The wheel shows number "<<randoms;
    if (randoms == choice) {
        money += bet;

        cout<<", which means that you win "<<bet<<" dollars! You currently have "<<money<<" dollars."<<flush<<endl;
    }
    else {
        money -= bet;

        cout<<", which means that you lose "<<bet<<" dollars. You currently have "<<money<<" dollars."<<flush<<endl;
    }

}

};

int main(int argc, const char * argv[])
{
srand(unsigned(time(0)));
Roulette a;
a.start_amount();

while (a.play == 0) {
    a.betting();
    a.choose_number();
    a.random_number();
    a.scenarios();
}

return 0;
}


void string_to_number(string input, double& destination) {
stringstream convert(input);
if ( !(convert >> destination) )
    destination = 0;
}
Måns Nilsson
  • 431
  • 1
  • 4
  • 16
  • I recommend not using floating-point numbers to store your money. I also recommend using the `` header and `std::stoi`. – chris Oct 16 '13 at 15:32
  • 2
    `randoms == choice` careful when doing any comparisons with doubles. They are not exactly what you might expecting them to be because not all numbers can be expressed with a double. – andre Oct 16 '13 at 15:32
  • 1
    First thought - you're a victim of binary floating point. The value `0.1`, for example, *cannot* be precisely represented in binary. The easiest workaround is to not work in fractional dollars, work in integer cents. –  Oct 16 '13 at 15:32
  • Without reading your question in detail, I would put money on the answer being [goldberg](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html). (Well, perhaps that is just the problem, and the answer is a suitably scaled `int`). – BoBTFish Oct 16 '13 at 15:33
  • 3
    @andre `double` represent an exact value, always. It may not be the value you would expect had you used real arithmetic, but then, doubles aren't reals. (On the other hand, if you know what you are doing, it's perfectly possible to do some forms of exact arithmetic using doubles.) – James Kanze Oct 16 '13 at 15:34
  • **READ THIS:** http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – Thomas Matthews Oct 16 '13 at 16:01

4 Answers4

4

This is not because program is subtracting wrong - it is because binary fractions and decimal fractions are not "fully mathematically compatible" - finite decimal fractions are often infinite periodic binary fractions.

So, for some decimal fractions like 0.15 there exist several valid double approximations, as the result of subtraction you got one of them (A) and as the result of conversion from string "0.15" you got another one (B). And by accident B appeared greater than A.

You should use integer cents, not double dollars to keep precise decimal rounding. More general solution is using some decimal number class (like this), which readily implements decimal fraction arithmetic using integers.

Some decimal (and binary) number classes implements arbitrary precision arithmetic - it solves task of having fixed point part larger than of hardware supported double type. You do not need this in applications where rounding to cents (2 decimal digits) is prescribed.

mas.morozov
  • 2,666
  • 1
  • 22
  • 22
  • That's very interesting. I didn't know that floating-point numbers (such as float and double) don't exactly represent numbers. But how would longer decimal values, such as pi and e, be correctly stored in an application then? – Måns Nilsson Oct 17 '13 at 07:16
  • 1
    @Måns - float and double *do* exactly represent numbers. They just don't exactly represent the same set of numbers that you could represent using a decimal representation. As for `pi` and `e`, these values are irrational, which implies they cannot be precisely represented in *any* base, decimal included. pi is not precisely 3, 3.14, 3.1415926 or whatever - `pi` and `e` both have an infinite number of digits, and the digits don't even cycle. Whatever representation of numbers you use, you can only *approximate* `pi` or `e`. –  Oct 17 '13 at 09:04
  • @Steve314 - If I've understood this correctly, the only way to exactly represent the same set of numbers is to use integers? Pi and e were poor examples by me, by the way. If we take the fraction 5/8 for example, which equals 0.625, how would you exactly represent that decimal number/value? – Måns Nilsson Oct 17 '13 at 10:12
  • @Måns - 5/8 can be represented exactly in binary too - 0.101. That's one lot of one half (0.1) plus one lot of one eighth (0.101). In fact any number that can be represented in binary can also be represented in decimal. To understand why, consider the digit values for digits after the point - 0.5, 0.25, 0.125, 0.0625 and so on, always with a finite number of decimal digits. As for which values can be represented in which bases, if you're really interested, look at prime factorization. I'm not an expert on this stuff, but I do know that the prime factors of the base are relevant. –  Oct 17 '13 at 10:51
  • @Måns - 10 has prime factors 2 and 5. 2 only has prime factor 2. Therefore, binary (base 2) cannot represent all numbers that decimal (base 10) can. `1/5` and `1/10` (and multiples of them like `2/5`) cannot be exactly represented in binary, but can be exactly represented in decimal. In a sense, the perfect base would have infinite repetitions of every prime in its prime factorization - if such a base could work, it could represent any rational number exactly. –  Oct 17 '13 at 10:56
  • 1
    @MånsNilsson - "the only way to exactly represent the same set of numbers is to use integers?" - No, another way is using decimal number class (which does the job with integers internally for you). Updated and corrected my answer accordingly. – mas.morozov Oct 17 '13 at 11:30
  • In addition to decimal number types and integers-with-scaling, another way is using a rational number type, if your language/library supports one. The definition of rational number is essentially a number that can be expressed as a ratio of integers - a (maybe topheavy) fraction. Any number that can be expressed finitely in any base can be expressed using a finite fraction (no points needed), so combined with a "big integer" type (for the top and bottom of the fraction), practically any *rational* number can be represented on a computer - but calculations will be a bit slow. –  Oct 17 '13 at 12:09
  • 1
    @Steve314 Using exact rational arithmetics, calculatuions can be *very* (not a bit) slow, because in general digit-size of numerators and denominators grows exponentially with the number of multiplications (divisions). That is why exact rational arithmetic is of very rare use. – mas.morozov Oct 17 '13 at 12:17
  • @mas - That general/worst case often doesn't happen - it depends on the application. If the result and intermediate values are all simple fractions, there's no exponential growth problem (though it's still "a bit" slow). But yes - I forgot that my tone of voice can't be heard through text. –  Oct 17 '13 at 12:52
1

Rounding errors can play havoc with math-intense programs, as mathematical operations can compound the error.

http://www.learncpp.com/cpp-tutorial/25-floating-point-numbers/

Mike Makuch
  • 1,788
  • 12
  • 15
1

For me I tried first to move out decimals by multiplying both by 100, for example, 0.99 will become 99. Then after subtracting, I simply put back the decimal point by dividing it by 100.

BroVic
  • 979
  • 9
  • 26
0

It is advised to not use floats for monetary calculations. Floats become imprecise quite quickly with big numbers due to their small size.

For monetary calculations, you may use Integers with a fixed scale (for example 100 becomes one dollar). This way you have 100% accuracy in the amount of decimal places you need.

Raildex
  • 3,406
  • 1
  • 18
  • 42