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
   32   33   34   35   36   37   38   39   40   41   42