views:

561

answers:

11

I'm trying to represent a simple set of 3 probabilities in C++. For example:

a = 0.1  
b = 0.2  
c = 0.7

(As far as I know probabilities must add up to 1)

My problem is that when I try to represent 0.7 in C++ as a float I end up with 0.69999999, which won't help when I am doing my calculations later. The same for 0.8, 0.80000001.

Is there a better way of representing numbers between 0.0 and 1.0 in C++?

Bear in mind that this relates to how the numbers are stored in memory so that when it comes to doing tests on the values they are correct, I'm not concerned with how they are display/printed out.

+8  A: 

How much precision do you need? You might consider scaling the values and quantizing them in a fixed-point representation.

Adam
+22  A: 

This has nothing to do with C++ and everything to do with how floating point numbers are represented in memory. You should never use the equality operator to compare floating point values, see here for better methods: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm

Evän Vrooksövich
He did ask about C++ methods specifically, and he did not ask anything related to comparing floating point values.
Marsh Ray
Yes he did. "... so that when it comes to doing tests on the values they are correct". I interpret this as meaning: comparing these values to other values.
Evän Vrooksövich
There are lots of ways to "test" floating point "values" that don't involve using the equality operator. Regardless, it wasn't his question.
Marsh Ray
+12  A: 

My problem is that when I try to represent 0.7 in C++ as a float I end up with 0.69999999, which won't help when I am doing my calculations later. The same for 0.8, 0.80000001.

Is it really a problem? If you just need more precision, use a double instead of a float. That should get you about 15 digits precision, more than enough for most work.

Consider your source data. Is 0.7 really significantly more correct than 0.69999999?

If so, you could use a rational number library such as:

http://www.boost.org/doc/libs/1%5F40%5F0/libs/rational/index.html

If the problem is that probabilities add up to 1 by definition, then store them as a collection of numbers, omitting the last one. Infer the last value by subtracting the sum of the others from 1.

Marsh Ray
Changing the precision from float to double doesn't solve the problem, it just postpones it.
drhirsch
There are plenty of applications for which float is not precise enough, yet double is sufficient.
Marsh Ray
I like the idea that the last value is "1 - sum(all other values)"
Martin York
+1  A: 

If you only need a few digits of precision then just use an integer. If you need better precision then you'll have to look to different libraries that provide guarantees on precision.

tloach
+1  A: 

Binary machines always round decimal fractions (except .0 and .5, .25, .75, etc) to values that don't have an exact representation in floating point. This has nothing to do with the language C++. There is no real way around it except to deal with it from a numerical perspective within your code.

As for actually producing the probabilities you seek:

float pr[3] = {0.1, 0.2, 0.7};
float accPr[3];
float prev = 0.0;
int i = 0;

for (i = 0; i < 3; i++) {
    accPr[i] = prev + pr[i];
    prev = accPr[i];
}

float frand = rand() / (1 + RAND_MAX);
for (i = 0; i < 2; i++) {
    if (frand < accPr[i]) break;
}
return i;
Paul Hsieh
"except .0 and .5" is either misleading or wrong, depending on how you want to look at it.
Roger Pate
You're right, I should say fractions whose denominators are powers of 2.
Paul Hsieh
+2  A: 

If you really need the precision, and are sticking with rational numbers, I suppose you could go with a fixed point arithemtic. I've not done this before so I can't recommend any libraries.

Alternatively, you can set a threshold when comparing fp numbers, but you'd have to err on one side or another -- say

bool fp_cmp(float a, float b) {
    return (a < b + epsilon);
}

Note that excess precision is automatically truncated in each calculation, so you should take care when operating at many different orders of magnitude in your algorithm. A contrived example to illustrate:

a = 15434355e10 + 22543634e10
b = a / 1e20 + 1.1534634
c = b * 1e20

versus

c = b + 1.1534634e20

The two results will be very different. Using the first method a lot of the precision of the first two numbers will be lost in the divide by 1e20. Assuming that the final value you want is on the order of 1e20, the second method will give you more precision.

int3
Why wouldn't you just write: return (a < b + epsilon);
micmoo
... whoops, how embarrassing. I've seen such code posted a couple of times and even laughed at it. I guess I wasn't thinking.
int3
and edited accordingly.
int3
+2  A: 

The tests you want to do with your numbers will be incorrect.

There is no exact floating point representation in a base-2 number system for a number like 0.1, because it is a infinte periodic number. Consider one third, that is exactly representable as 0.1 in a base-3 system, but 0.333... in the base-10 system.

So any test you do with a number 0.1 in floating point will be prone to be flawed.

A solution would be using rational numbers (boost has a rational lib), which will be always exact for, ermm, rationals, or use a selfmade base-10 system by multiplying the numbers with a power of ten.

drhirsch
A: 

yeah, I'd scale the numbers (0-100)(0-1000) or whatever fixed size you need if you're worried about such things. It also makes for faster math computation in most cases. Back in the bad-old-days, we'd define entire cos/sine tables and other such bleh in integer form to reduce floating fuzz and increase computation speed.

I do find it a bit interesting that a "0.7" fuzzes like that on storage.

Cranky
7/10 fuzzes like that in base-2 representation just like 1/3 or 1/7 fuzz in base-10 representation. Nothing unsurprising with that.
Roberto Bonvallet
Really bad answer. This should be your "best answer"
Seth Illgard
A: 

I'm sorry to say there's not really an easy answer to your problem.

It falls into a field of study called "Numerical Analysis" that deals with these types of problems (which goes far beyond just making sure you don't check for equality between 2 floating point values). And by field of study, I mean there are a slew of books, journal articles, courses etc. dealing with it. There are people who do their PhD thesis on it.

All I can say is that that I'm thankful I don't have to deal with these issues very much, because the problems and the solutions are often very non-intuitive.

What you might need to do to deal with representing the numbers and calculations you're working on is very dependent on exactly what operations you're doing, the order of those operations and the range of values that you expect to deal with in those operations.

Michael Burr
+1  A: 

The issue here is that floating point numbers are stored in base 2. You can not exactly represent a decimal in base 10 with a floating point number in base 2.

Lets step back a second. What does .1 mean? Or .7? They mean 1x10-1 and 7x10-1. If you're using binary for your number, instead of base 10 as we normally do, .1 means 1x2-1, or 1/2. .11 means 1x2-1 + 1x2-2, or 1/2+1/4, or 3/4.

Note how in this system, the denominator is always a power of 2. You cannot represent a number without a denominator that is a power of 2 in a finite number of digits. For instance, .1 (in decimal) means 1/10, but in binary that is an infinite repeating fraction, 0.000110011... (with the 0011 pattern repeating forever). This is similar to how in base 10, 1/3 is an infinite fraction, 0.3333....; base 10 can only represent numbers exactly with a denominator that is a multiple of powers of 2 and 5. (As an aside, base 12 and base 60 are actually really convenient bases, since 12 is divisible by 2, 3, and 4, and 60 is divisible by 2, 3, 4, and 5; but for some reason we use decimal anyhow, and we use binary in computers).

Since floating point numbers (or fixed point numbers) always have a finite number of digits, they cannot represent these infinite repeating fractions exactly. So, they either truncate or round the values to be as close as possible to the real value, but are not equal to the real value exactly. Once you start adding up these rounded values, you start getting more error. In decimal, if your representation of 1/3 is .333, then three copies of that will add up to .999, not 1.

There are four possible solutions. If all you care about is exactly representing decimal fractions like .1 and .7 (as in, you don't care that 1/3 will have the same problem you mention), then you can represent your numbers as decimal, for instance using binary coded decimal, and manipulate those. This is a common solution in finance, where many operations are defined in terms of decimal. This has the downside that you will need to implement all of your own arithmetic operations yourself, without the benefits of the computer's FPU, or find a decimal arithmetic library. This also, as mentioned, does not help with fractions that can't be represented exactly in decimal.

Another solution is to use fractions to represent your numbers. If you use fractions, with bignums (arbitrarily large numbers) for your numerators and denominators, you can represent any rational number that will fit in the memory of your computer. Again, the downside is that arithmetic will be slower, and you'll need to implement arithmetic yourself or use an existing library. This will solve your problem for all rational numbers, but if you wind up with a probability that is computed based on π or √2, you will still have the same issues with not being able to represent them exactly, and need to also use one of the later solutions.

A third solution, if all you care about is getting your numbers to add up to 1 exactly, is for events where you have n possibilities, to only store the values of n-1 of those probabilities, and compute the probability of the last as 1 minus the sum of the rest of the probabilities.

And a fourth solution is to do what you always need to remember when working with floating point numbers (or any inexact numbers, such as fractions being used to represent irrational numbers), and never compare two numbers for equality. Again in base 10, if you add up 3 copies of 1/3, you will wind up with .999. When you want to compare that number to 1, you have to instead compare to see if it is close enough to 1; check that the absolute value of the difference, 1-.999, is less than a threshold, such as .01.

Brian Campbell
A: 

Depending on the requirements of your applications any one of several solutions could be best:

  1. You live with the inherent lack of precision and use floats or doubles. You cannot test either for equality and this implies that you cannot test the sum of your probabilities for equality with 1.0.

  2. As proposed before, you can use integers if you require a fixed precision. You represent 0.7 as 7, 0.1 as 1, 0.2 as 2 and they will add up perfectly to 10, i.e., 1.0. If you have to calculate with your probabilities, especially if you do division and multiplication, you need to round the results correctly. This will introduce an imprecision again.

  3. Represent your numbers as fractions with a pair of integers (1,2) = 1/2 = 0.5. Precise, more flexible than 2) but you don't want to calculate with those.

  4. You can go all the way and use a library that implements rational numbers (e.g. gmp). Precise, with arbitrary precision, you can calculate with it, but slow.

Sebastian