Skip to main content
GameDev.net gamedev.net
🔒 Locked

Problems with C++ float type

Started by MajinMusashi Feb 24, 2005 at 8:29 AM 13 replies 2.5k views
Original Post
MajinMusashi
MajinMusashi
Hi! I'm using Visual C++ 6 (with the last Service Pack installed), and got some strange results working with floating point numbers. Just see the example bellow:

coordComplement.x = 2.9f;
blockSize = 29.0f;
coordComplement.x -= blockSize / 10.0f;
// coordComplement.x == 9.53674e-008 (WHAT?!)

coordComplement.x = 2.9f;
blockSize = 29.0f;
coordComplement.x -= 29.0f / 10.0f;
// coordComplement.x == 0.0f (OK!!)

Is that a rounding problem? How can I solve it without using double variables? Thanks!!
Zipster
Zipster
2.9 is a dirty number in IEEE, so I wouldn't be surprised if it's rounding error. As for why the second one works better than the first, it might have to do with the compiler optimizing away the operation, I dunno.
Xai
Xai
The 2 examples you posted should generate identical floating point code, so I'd check your generated program to see the differences ... also try building in debug mode and see if the results are the same (always a chance for bad optimizations) and check your compilers settings for floating point code generation.

Second, the first answer does not seem wrong when you understand floating point:

2.9f is stored as something NEAR 2.9 ... (because binary cannot store 0.1 or 0.9 perfectly (along with many other numbers) ...

so you are saying

coordComplement.x = ~2.9
blockSize = ~29
~2.9 -= (~29 / 10)

and the answer you got is like:

0.0000000953674

which is pretty darn close.

You will find that this problem is very well known and is inheirent in all floating point calculations. In addition, floating point is known to be very tricky when doing calculations on 2 large numbers that yeild a significantly smaller number (such as say 1234.5678987 - 1234.5678986) ...
MajinMusashi
MajinMusashi
Here is a bit of more detailed information about my problem:

Real code:
// offset == blockSize * 0.1f == 2.9f;// coordComplement.x == 2.9f;if ( coordComplement.x != 0.0f ) {	if ( coordComplement.x > 0.0f ) {		coordComplement.x -= offset;	} else {		coordComplement.x += offset;	}}// coordComplement.x == 2.38419e-006 instead of 0.0f



And the corresponding assembly code:

113: if ( coordComplement.x > 0.0f ) {
00402C20 mov ecx,dword ptr [ebp-4]
00402C23 fld dword ptr [ecx+24h]
00402C26 fcomp dword ptr [__real@4@4002f000000000000000 (0041f04c)]
00402C2C fnstsw ax
00402C2E test ah,41h
00402C31 jne C_PieceL::update+57h (00402c47)
114: coordComplement.x -= offset;
00402C33 mov edx,dword ptr [ebp-4]
00402C36 mov eax,dword ptr [ebp-4]
00402C39 fld dword ptr [edx+24h]
00402C3C fsub dword ptr [eax+48h]
00402C3F mov ecx,dword ptr [ebp-4]
00402C42 fstp dword ptr [ecx+24h]
115: } else {
00402C45 jmp C_PieceL::update+69h (00402c59)
116: coordComplement.x += offset;
00402C47 mov edx,dword ptr [ebp-4]
00402C4A mov eax,dword ptr [ebp-4]
00402C4D fld dword ptr [edx+24h]
00402C50 fadd dword ptr [eax+48h]
00402C53 mov ecx,dword ptr [ebp-4]
00402C56 fstp dword ptr [ecx+24h]
117: }

Can you help me even more? Is there any compiler option that correct this rounding error? Is there a simple workaround?
Thanks!!!!!

Xai: I've tried only in debug mode (no optimizations), and those values were caught by the VC++ watcher.
Agony
Agony
Quote:
Original post by MajinMusashi
Can you help me even more? Is there any compiler option that correct this rounding error? Is there a simple workaround?

C++ FAQ Lite: [29.16] Why is floating point so inaccurate?

That's just the way floating point math works. double will make the problem less apparent by adding more accuracy, but the problem is still there, since float and double both rely on the exact same principles.

You could always manually round your numbers to the nearest whatever-decimal place; for example, this would round to the nearest 0.001:
n = floor(n * 1000.0f) / 1000.0f;
Used in the right situations, this can help to eliminate the inaccurate results you might get. But this will actually hurt your accuracy if you don't actually plan on getting "round" numbers in base 10.

For straight up math, ignore the inaccuracies. For comparisons of equality, don't compare for exact equality, but make sure that the values are very close to each other. For displaying, round the numbers. That's about the best advice I can give you. (Unless highly accurate numbers are an absolute necessity. In that case, find a BigNum library, such as GMP. But it isn't going to be nearly as fast as float or double math.)
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas nly, and not for things themselves." - John Locke o
Mantear
Mantear
2.9 and -2.9 are completely different numbers (one isn't the other with just a '-' in front of it). When you try to approximate the values using floats, the closest approximation available from 32 bits will be different for the positive and the negative number. 2.9 might be approximated as 2.89475234 and -2.9 might be approximated as -2.90123873 (numbers pulled out of nowhere, but you get the idea).

Doing something as simple as:
x = 2.9;y = -2.9;z = x + y;

and you're not guaranteed that z will = 0.0

Now, if we had computers that weren't binary but decimary and worked on powers of 10... it would work. Or, if you used binary numbers for everything, there's no approximation, so you'll always get the answer you expect. The short of it all is there's no way around the "fuzziness" of floats/doubles.
smart_idiot
smart_idiot
Floating point numbers aren't accurate. Either live with it or use something else. If you decide to live with it, then just be aware that all your results are approximations, and that not all decimal numbers can be represented nicely in binary or vica versa. 2.38419e-006 is 0.00000238419, which is pretty close to the correct answer, and is probably the best you can hope in most cases.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other
Osaou
Osaou
If you want the same accuracy the int type gives you, you could look into fixed point math...
Basically, it gives the accuracy of integers (no approximations), with decimal support. The reason it isn't standardized is because you lose the ability to represent large numbers...
JohnBolton
JohnBolton
9.53674e-008 = 0.0000000953674
2.38419e-006 = 0.00000238419

Those are pretty close to zero.

Your problem is that you expect exact results from floating-point operations. You don't get exact results using floating point. Not even doubles will give you exact results.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Deyja
Deyja
For the record, the reason your second version ( CoordComplement.x -= 29.0f / 10.0f; ) is more accurate is because of the optimizer. The optimizer just replaces that with -= 2.9f. Furthermore, if one of the operands had been a variable, the optimizer can still work on the other value. Your FPU does all it's operations in an 80-bit format, regardless of wether the result is stored in a float or double. The compiler can stick the hardcoded value directly into an 80 bit register, so it looses less precision than it would by first being stored as a float.
MajinMusashi
MajinMusashi
Hi all!
The location sent by Agony gave me good and interesting details about floating point numbers, and the solution that is more suitable to my needs is:
inline bool isEqual( double x, double y ) {   const double epsilon = 0.00001;   return abs( x - y ) <= epsilon * abs( x );}if ( !isEqual( coordComplement.x, 0.0f ) ) {    if ( coordComplement.x < 0.0f ) {        coordComplement.x += offset;    } else {        coordComplement.x -= offset;    }}


Thanks for all your help!
Goodbye!!!
iMalc
iMalc
Quote:
Original post by Mantear
2.9 and -2.9 are completely different numbers (one isn't the other with just a '-' in front of it). When you try to approximate the values using floats, the closest approximation available from 32 bits will be different for the positive and the negative number. 2.9 might be approximated as 2.89475234 and -2.9 might be approximated as -2.90123873 (numbers pulled out of nowhere, but you get the idea).

Doing something as simple as:
x = 2.9;y = -2.9;z = x + y;

and you're not guaranteed that z will = 0.0

Now, if we had computers that weren't binary but decimary and worked on powers of 10... it would work. Or, if you used binary numbers for everything, there's no approximation, so you'll always get the answer you expect. The short of it all is there's no way around the "fuzziness" of floats/doubles.
Bollucks! The only difference between the representation of -2.9 and +2.9 is that the sign bit is different. The exponent and mantissa parts will be identical.
MaulingMonkey
MaulingMonkey
Quote:
Original post by MajinMusashi
Is that a rounding problem?

Yes.
Quote:
How can I solve it without using double variables?

A very generalized question - it depends on the problem. More info?

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.