Tag Archives: TGB Torque Game Builder

Oh floating-point comparisons, oh floating-point comparisons - why do you float above causality :(

You know, actually I always wondered why people had problems with understanding why you need epsilon tests for float comparisons: you simply lose precision when you calculate something because floats aren't lossless and so you can't simply use == to compare them, because chances are high that two floats that ought to be equal won't be equal bit-wise. Thus you use epsilon tests instead and only check if both are almost the same (there are many techniques for this though).

I think this concept is easily understandable but what about comparisons for floats that are bit-wise equal?

Yes, yes, yes! No, no, no!

So let's take a look at this code:

// Update Current Time.
mCurrentTime += elapsedTime;

// Check if the animation has finished.
if ( !mConfigDataBlock->mAnimationCycle && mCurrentTime >= mTotalIntegrationTime )
{
// Animation has finished.
mAnimationFinished = true;

// Are we restoring the animation?
if ( mAutoRestoreAnimation )
{
// Yes, so play last animation.
playAnimation( mLastAnimationName, false );
}
else
{
// No, so fix Animation at end of frames.
mCurrentTime = mTotalIntegrationTime - (mFrameIntegrationTime * 0.5);
}
}

// Update Current Mod Time.
mCurrentModTime = mFmod( mCurrentTime, mTotalIntegrationTime );

// Calculate Current Frame.
mCurrentFrameIndex = (S32)(mCurrentModTime / mFrameIntegrationTime);

This is from TGB's t2dAnimationController::updateAnimation function. I had a very weird bug that caused an animation with 5 frames (base 0) and that shouldn't cycle to run through the following frames:
start/0 -> 1 -> 2 -> 3 ->0 -> 4/end

Just so you know some initial conditions:

  • mConfigDataBlock->mAnimationCycle = false, so !mConfigDataBlock->mAnimationCycle = true
  • mAutoRestoreAnimation = false

So obviously the if-block doesn't get executed for some unknown reason but the mFmod call still wraps the time around and the index jumps to 0 for one frame. Then mCurrentTime increases even more, the if-block gets executed and the time is reset to a value that makes it jump to the last frame (ie. it doesn't wrap now) and then the animations correctly ends.

Now this sounds easy to fix and it is. Simply add an epsilon test and you're fine off and everything works correctly. But to make sure that I've actually fixed the bug for the right reason, I did some more test runs with the original code and echoed the value of the floats to determine when the bug occured.

The first little shock was: it echoed: "3.00000 >= 3.00000".

Read more »