Page 1 of 1

Problems with node positions and precision with fmod()...

Posted: Mon Oct 13, 2008 11:36 pm
by Rytz
Hey All:

The problem I'm having originally stemmed from times where I was trying to set a node position but it kept getting changed automatically whenever ISceneManager->drawAll() was called. This wouldn't happen all the time, so it was pretty frustrating and time consuming to catch and understand.

For example, I would do something similar to this:

Code: Select all

node->setPosition(core::vector3df(123.456789, 0, 0));
printf("\n_DEBUG_POS_1_: %f\n", node->getPosition().X);
dvWindow->getSceneManager()->drawAll();
printf("_DEBUG_POS_2_: %f\n", node->getPosition().X);
The 1st debug print would show the correct position, but the 2nd would show a slightly changed number, always differing by the hundredth-thousandth or millionth (0.0000##).

So, as a work around I was trying to use fmod() to drop some of the decimal places and make the number more rounded. I ran into the exact same situation with this as well:

CALC:

Code: Select all

core::vector3df v3dNewTarget(123.456789, 0, 0);
printf("\n\n_NEW_TARGET_BEFORE_: %f\n", v3dNewTarget.X);
printf("FMOD: %f\n", fmod(v3dNewTarget.X, 0.0001));
printf("CALC: %f\n", v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001));
f32 temp = v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001);
printf("VAR: %f\n", temp);
v3dNewTarget.X = v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001);
printf("_NEW_TARGET_AFTER_: %f\n\n", v3dNewTarget.X);
RESULT:

Code: Select all

_NEW_TARGET_BEFORE_: 123.456787
FMOD: 0.000087
CALC: 123.456700
VAR: 123.456703
_NEW_TARGET_AFTER_: 123.456703
The printf() of the calculation shows the correct number but when stored in any type of variable (I've tried floats, doubles, long doubles and f32), it always ends up different.

Maybe I'm completely missing something obvious and my mind is playing tricks on me. Is there a way to overcome this if it isn't something easy / obvious? My main issue with all of this is that I'm doing something client / server based and the server is storing the correct coordinates but sometimes (like above) the client changes the coordinate and the target is never reached.

Edit: I did find [THIS] thread earlier that I figured might have something to do with the sitation.

Thanks in advance for any input.

Posted: Tue Oct 14, 2008 2:28 am
by bitplane
ISceneNode::RelativeTranslation holds the position of the node, this doesn't even get changed by Irrlicht internally. Are you sure you weren't checking getAbsolutePosition(), which is calculated (and gets rounded) during drawAll?

Also, you're using fmod in the decimal system. Like hybrid said in the other thread they don't align to that system.
Finally, 123.456789 doesn't fit in a float! There's only 24 bits of precision in an IEEE 32-bit float and 123,456,789 is almost ten times bigger than 16,777,216.

To solve your problem, it would make sense to lock your input boxes to two decimal places. This is the nicest way to hide the ugliness from the user, bar storing everything in text.

Posted: Tue Oct 14, 2008 2:53 am
by Rytz
Thanks for the reply bitplane.
bitplane wrote:ISceneNode::RelativeTranslation holds the position of the node, this doesn't even get changed by Irrlicht internally. Are you sure you weren't checking getAbsolutePosition(), which is calculated (and gets rounded) during drawAll?
Nope - using getPosition() as shown in the example above. If you're saying it never gets changed internally then I'm even more confused at this point.
bitplane wrote: Also, you're using fmod in the decimal system. Like hybrid said in the other thread they don't align to that system.
Finally, 123.456789 doesn't fit in a float! There's only 24 bits of precision in an IEEE 32-bit float and 123,456,789 is almost ten times bigger than 16,777,216.
I'll admit, I didn't read that entire thread - what data type can I store "123.456789" in? A double? I got the same result that I did with a float. I guess I'm pretty confused at this point since even vector3df's are designed to store these numbers and they're getting changed on me (shown above).
bitplane wrote: To solve your problem, it would make sense to lock your input boxes to two decimal places. This is the nicest way to hide the ugliness from the user, bar storing everything in text.
The coordinates aren't being manually entered in. I'm taking coordinates straight from a vector3df on the server, passing them over the network to the client, and setting a new position with those coordinates. Nothing is being changed or lost in the network transfer, just "randomly" after ISceneManager->drawAll().

Posted: Fri Oct 17, 2008 10:27 pm
by bitplane
If you're using MSVC you can set a breakpoint when some address in memory changes. Do this and track down where the variable is being changed, if it's somewhere in Irrlicht then I'd be surprised.

Posted: Sat Oct 18, 2008 3:12 am
by vitek
I believe you're running into reasonable behavior.

A 32-bit floating point number has 6 or 7 digits of precision (123.4567??), so what you see after those 7 digits is essentially garbage. It shouldn't change once the values have been stored in memory or on the stack.

Another thing to take into consideration is that x86 cpus do floating point arithmetic in extended floating point registers, and the result is truncated/rounded when it is written back to memory.

Code: Select all

core::vector3df v3dNewTarget(123.456789, 0, 0); 
printf("\n\n_NEW_TARGET_BEFORE_: %f\n", v3dNewTarget.X);

// _NEW_TARGET_BEFORE_: 123.456787
// expected because there are only 6 or 7 digits stored accurately
// and this is the value stored in the 32-bit float on the stack

printf("FMOD: %f\n", fmod(v3dNewTarget.X, 0.0001)); 

// FMOD: 0.000087

printf("CALC: %f\n", v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001)); 

// CALC: 123.456700

f32 temp = v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001); 

// here the value above is stored from the extended 80-bit floating point
// representation to a 32-bit float on the stack. there is going to be some
// rounding when this happens.

printf("VAR: %f\n", temp); 

// VAR: 123.456703
// this is the value you get after the rounding. again, the last two digits
// are essentially garbage. they are an artifact of the floating point format

v3dNewTarget.X = v3dNewTarget.X - fmod(v3dNewTarget.X, 0.0001); 
printf("_NEW_TARGET_AFTER_: %f\n\n", v3dNewTarget.X); 

// _NEW_TARGET_AFTER_: 123.456703
// this is expected. the result of the above calculation should be expected
// to be the same as this calculation.
The bottom line is that you are just expecting to much of the floating point system. It isn't infinitely accurate. Either modify your system to use smaller numbers, switch to a format with more precision (double or long double), or expect less.

Travis

Posted: Wed Oct 22, 2008 12:59 am
by Rytz
Thanks guys - I honestly didn't know this would be an issue. How does Irrlicht deal with this internally?

Posted: Wed Oct 22, 2008 1:22 am
by vitek
It doesn't. Having a position error of .0001 units is not significant. Obviously, the further you get from the origin of the coordinate system, the more error there will be, but for small offsets the error is not even noticed.

When dealing with large coordinate systems, some engines use special systems to deal with the precision issues. They may store a 32-bit integer to represent the whole part and float to represent the fractional part of a distance. They might split the environment up into areas, and when the camera moves outside one area it will offset the position of all of the nearby objects so that the camera remains near the origin of the coordinate system.

There are lots of ways to deal with this, each with advantages and disadvantages. You just have to read about them and decide which you want to implement. Of course it wouldn't make any sense to deal with this 'problem' if it isn't noticeable in your application.

Travis