Page 37 - Developer
P. 37
R
b
//
E
c
U
od
INNER PR
INNER PRodUcT // aUTHoR dawsoN
T
c
U
precision of your input numbers range of just a few seconds. We
listing 1 is perfectly maintained, but the can therefore assume that time
relative precision can get arbitrarily and intervalend are relatively
a fragment of code used for dealing with particles—that unfortunately bad. In the example of measuring a close to each other, whereas
magnifies floating-point math inaccuracy. person’s height when they are on a intervalLength is much smaller.
skyscraper, the real problem is the In Listing 1, the first subtraction
float CalcTBad(float intervalEnd, float intervalLength, float time) initial numbers, whose absolute is of a small number from a large
{ error (about .38 m) is insufficient number, so most of the digits of
float intervalStart = intervalEnd - intervalLength; for the ultimate goal of measuring intervalLength will be lost—they
float t = (time - intervalStart) / intervalLength; a person’s height. This inadequacy won’t affect the result. Then we
return t; becomes manifest when the values subtract intervalstart from time,
} are subtracted, as the subtraction and we cancel out most of their
makes the relative error about 200 most significant digits. The first
listing 2 times worse. subtraction tosses away low-order
The other problem with digits, and the second subtraction
a modified, more accurate version of Listing 1, reworked to be more accurate. addition and subtraction occurs cancels out high-order digits. That
when you are adding numbers of leaves very few digits, and even
float CalcTGood(float intervalEnd, float intervalLength, float time) dissimilar magnitudes. Unlike with when time is clearly in range, the
{ multiplication and division, the rounding means that the result
float timeToIntervalEnd = intervalEnd - time; digits of the smaller number may in sometimes ends up being outside
float t = (intervalLength - timeToIntervalEnd) / this case be completely ignored. of the 0.0 to 1.0 range!
intervalLength; A better way to express
return t; Two wrongs this algorithm is to start by
} don’T make a righT subtracting the two times. In
» The worst-case scenario is if you most cases these two times will
listing 3 add a small number to a big number be within a factor of two of each
(thus losing the least-significant other so this will be perfectly
sample relative float comparison code. digits of the small number) and accurate—we can’t do better than
then subtract a similarly valued that. We then subtract the result
bool AlmostEqualRelative(float A, float B, float maxRelDiff = big number (thus losing the from intervalLength, and since
FLT_EPSILON) most-significant digits of the big these values are likely to have
{ number). In this case you end up similar values, we don’t lose much
// Calculate the difference. with very few significant digits— accuracy. In many cases, we’ll
float diff = fabs(A - B); perhaps none. In other words, this only need to round in the final
A = fabs(A); is really bad: division, and the result has many
B = fabs(B); more digits of precision than
// Find the largest Result = (Big1 + Small) - - Big2 the original calculation. See the
float largest = (B > A) ? B : A; improved code in Listing 2; the
Understanding these behaviors net result is that the fixed code
if (diff <= largest * maxRelDiff) sometimes lets you significantly is perfectly stable, and the client
return true; improve your code. As an example, code no longer needs to clamp the
return false; the function in Listing 1 is result into the 0.0 to 1.0 range.
} representative of real code from
a game I was working on. It takes Comparing fLoaTs
in two numbers that represent » Most developers know that
an interval of time, and another comparing floats for equality
operations are so much more all equally accurate. They are number that represents a time is not a good idea. With some
difficult than addition to do by all guaranteed to be correctly within that time interval. The exceptions, floating-point math
hand. But despite our intuition’s rounded (by default this is round- function’s job is to return a value t operations are not exact, error
misgivings, floating-point to-nearest, tie goes to even). So from 0.0 to 1.0 representing how accumulates over multiple steps,
multiplication and division are why is subtraction of same-signed far through the interval time is. and you probably won’t get
deliciously safe and stable, and numbers so problematic? This isn’t a great design for multiple exactly the result you wanted.
cause virtually no precision loss. If the values of the floats being reasons, but I’m going to ignore It’s easy to say that you
Even though we have to toss subtracted are within a factor of that for now and focus on the shouldn’t do an equality test,
away half of the digits of the two of each other, then the result implementation. but saying what you should do
result, the relative error after is exact. It’s pretty cool to have This code was used for dealing instead is much trickier. There
multiplication is increased very such a concrete guarantee about with particles, so after the game are many examples of floating-
little, if at all (except in the case the chaotic world of floats. And has been running for a while it point equality tests with epsilons
of overflow or underflow). yet you may have zero significant is safe to say that the end of the (comparison tolerances) so small
It turns out that IEEE addition, digits left, because most or all of interval will have a value in the that they are just fancy-looking,
subtraction, multiplication, division, the digits may have been canceled hundreds or thousands, while more-expensive equality tests,
and square-root operations are out. In this case, the absolute the interval length will have a or epsilons so large that that
www.gdmag.com 35