Demons infesting compiler

Started by
9 comments, last by MonkeyCookie 19 years, 5 months ago
There is probably a very simple reason for this, but it really doesn't make sense to me. This code: FLOAT dist_a = sqrtf( d ); // d == 99.999965f FLOAT dist = dot - dist_a; // dot == dist_a == 9.9999990f return dist; returns 0.0f While this code: return dot - sqrtf( d ); returns -1.9073484e-007f Woo hoo! The function is inline, if that makes any difference. Anyone know why this is happening? How I know this won't happen somewhere in the other thousands of lines of code I wrote without knowing about this issue? [disturbed] Feel free to move this to general programming, if it should be. I'm not sure where demons really fit [smile] edit: adding 1 to -1.9073484e-7 with windows calculator turns it to 0.999999. This is good. But why -1.9073484e-007f and not -0.00001 or such? It's screwing up a whole host of other algorithms, and I don't understand why.
Advertisement
Well....

Did you SET the value of d explicitly in code, e.g., did you write somewhere:

d = 99.999965f;


before going on to calc dist_a?

And, did you SET the value of dot to be exactly dist_a, e.g., did you write somewhere in code:

dot = dist_a;


If you did, then you've set yourself up to get exactly dot - dist_a = 0.

I suspect you did do something like this. The reason for the 1e-7 weirdness is, ultimately, due to roundoff error. If you calculate some dot product to get dot, using a set of operations, then some other set of operations to get d and dist_a, the different operations to get each value will result in very slight differences in the least significant digits (the ones furthest to the right of the decimal point) and this leads to the very small, but nonzero difference.

The reason I suspect you've created an artificially perfect test with the code you present is the following: your dot = dist_a = 9.999999 is NOT the square root of your d = 99.999965! If you take 9.999999 and square it, you get 99.99998 (not exact). The actual square root of 99.999965 is 9.9999982, in floating point precision, which isn't particularly accurate (square it and you'll come up short).

Graham Rhodes Moderator, Math & Physics forum @ gamedev.net
Nah, I didn't set any values manually. They are from a sphere-intersect test:

FLOAT dot = ray_direction.Dot( offset_to_sphere );
FLOAT d = dot * dot - ( offset_to_sphere.GetLengthSq() - (sphere_radius * sphere_radius) );

As for the 9.999999 square root, I may have picked the wrong number or rounded the value from the break point value. Sorry about that.

edit: dot was 9.9999990f. I just assumed dist_a was the same because of the 0 return value. Still, my mistake.

Since I did manually set the position of the sphere and a line which becomes calculated into a different angled ray, I do know that the distance from the ray origin to the sphere surface is very close to zero.

I'm still not sure why the same math is giving different results just because I store the values into a temporary variable? Confusing.

Float errors make math coding a real headache. Constantly have to add tiny buffers to let rounding errors slip through :(
This is somewhat compiler dependent, but intermediate values of floating point numbers are not necessarily of the same type/precision as the input values. For example, intermediate values of floating point calculations under MSVC (at least more recent versions) is are stored by default in a 53-bit precision format. So by storing the value into floating point variable you may be losing information that is represented by the intermediate value; hence the difference changes.

Your compiler may specify a way to change the default precision of intermediate floating point computation values.
Thanks for the suggestion, I'll look through the compiler settings to see if I can find that [smile]

Oh, and the reason for the other routines messing up was where I forgot to allow for error on checking for less than zero. The -1.9073484e-007f value doesn't seem to bother the compiler. I guess I'm still pretty new to this floating point math deal.
The real problem is in how you are comparing floating point numbers. With integers (3/2)*2!=(3*2)/2. Same way with float. The rounded that takes place depends upon the order of operations. So rather than checking if things are exactly equal you check if they are approximately equal. When dealing with numbers in the 10's a number near 10-7 should be considered zero.
Keys to success: Ability, ambition and opportunity.
I'm not sure how I would always know what range of numbers I'm dealing with. Could be 100's, could be 1000's, could be 0-1. It all depends on the velocity, shape, and size of the objects.

I would prefer to use extremely large floating point storage space, if it meant nearly perfect results [smile]

Unfortunately, DirectX isn't compatible with 256 bit floats [grin]
I don't see how it could be negative!
with the values you're giving us:
dot    = 9.9999990f = 0x411fffffdist_a = 9.9999980f = 0x411ffffedot - dist_a = (not negative)

it's only 1 bit of precision, but no fpu will ever tell you that it's actually negative! Whatever rounding happened before this point, if you start with those values, then subtract them it can't be negative.

Can you generate the assembly output for the 2 cases (i.e. via temporary vs straight return)? Have you looked at the difference between debug and optimized compilations? Optimizations on should bypass temporaries completely.
A large float wouldn't eliminate the problem. Numbers like 1/3 or 1/10 in binary take an infinite number of digits. No matter how large the representation there are more numbers that cannot be represented exactly than that can be. A billion byte float, a billion, billion byte float, doesn't matter how big. There will still be rounding, there will be no significant differance in the probability that a give number is rounded and exact comparisions will still fail as a result.

There is a big benefit to limiting your scale. That is that it saves you a multiplication which saves you time. If you do not know the scale of A and B then fabs(A-B)<fabs(A)*epsilon is the way to check if they are equal assuming neither A nor B are zero. If either are zero then you can't tell from A and B what the scale of the numbers are and thus where the cutoff for zero should be.
Keys to success: Ability, ambition and opportunity.
I've had a very similar problem a while ago with MSVC 7. If I remember correctly, using double instead of float fixed it for me, couldn't figure out why though.

This topic is closed to new replies.

Advertisement