The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 4,184 other subscribers

Why is the result of RoundTo(87.285, -2) => 87.28 – Stack Overflow

Posted by jpluimers on 2011/03/08

Programmers on all sorts of platforms get this wrong all the time (I admit having done this in bad ways myself too).

In short: Don’t expect floating point values in a computer to be represented as decimals.

Rob Kennedy wrote a very nice answer on this:

The exact value 87.285 is not representable as a floating-point value in Delphi. A page on my Web site shows what that value really is, as Extended, Double, and Single:

87.285 = + 87.28500 00000 00000 00333 06690 73875 46962 12708 95004 27246 09375

87.285 = + 87.28499 99999 99996 58939 48683 51519 10781 86035 15625

87.285 = + 87.28500 36621 09375

And David Heffernan points to the best link you can get on this topic:

The classic reference on floating point is What Every Computer Scientist Should Know About Floating-Point Arithmetic.

For currency based calculations, if indeed this is, you should use a base 10 number type rather than base 2 floating point. In Delphi that means `Currency`.

–jeroen

via delphi – Why is the result of RoundTo(87.285, -2) => 87.28 – Stack Overflow.

14 Responses to “Why is the result of RoundTo(87.285, -2) => 87.28 – Stack Overflow”

  1. […] […]

  2. Louis Kleiman said

    I was just reading the help:

    From the RAD Studio 2010 Help on RoundTo():

    RoundTo uses “Banker’s Rounding” to determine how to round values that are exactly midway between the two values that have the desired number of significant digits. This method rounds to an even number in the case that AValue is not nearer to either value.

    The following examples illustrate the use of RoundTo:
    Expression Value
    RoundTo(1234567, 3) 1235000
    RoundTo(1.234, -2) 1.23
    RoundTo(1.235, -2) 1.24
    RoundTo(1.245, -2) 1.24

    Note: The behavior of RoundTo can be affected by the Set8087CW procedure or SetRoundMode function.

    OK, but I am wrong. Thanks for straightening me out. I trusted the letter of the help at the surface without looking deeper.

    Just as a side note, SetRoundMode appears to have no effect on the results of the example David put out.

    Thanks, everyone. I’ll go back into my corner now…

    • jpluimers said

      Don’t worry we’re not mad at you ;-)

      Please file a QC bug report (at http://qc.embarcadero.com) on the documentation issue; you are totally right that it gives you the wrong impression.

      –jeroen

    • David Heffernan said

      Banker’s rounding could only be relevant if the routine was using a decimal data type rather than a binary one. SetRoundMode appears to be irrelevant because RoundTo sets the control word. It does indeed appear to be a documentation bug. Older versions of Delphi used a very different implementation of RoundTo. I’m looking at D6 for example and the implementation is different, will be affected by round mode, doesn’t appear to do banker’s rounding, and has the same documentation. Who knew?

  3. Louis Kleiman said

    Come on, folks, read the documentation. The answer to this has nothing to do with inexact representation of floating point numbers and everything to do with the fact that RoundTo() ALWAYS rounds a 5 to the nearest EVEN digit.

    • jpluimers said

      It doesn’t; it depens on the rounding mode. Lot’s of DLLs even explicitly twiddle with the rounding mode (and other floating point settings) in an unwanted way.
      –jeroen

    • David Heffernan said

      @Louis

      var
      d: Double;

      d := 87.205; Writeln(RoundTo(d, -2));
      d := 87.215; Writeln(RoundTo(d, -2));
      d := 87.225; Writeln(RoundTo(d, -2));
      d := 87.235; Writeln(RoundTo(d, -2));
      d := 87.245; Writeln(RoundTo(d, -2));
      d := 87.255; Writeln(RoundTo(d, -2));
      d := 87.265; Writeln(RoundTo(d, -2));
      d := 87.275; Writeln(RoundTo(d, -2));
      d := 87.285; Writeln(RoundTo(d, -2));
      d := 87.295; Writeln(RoundTo(d, -2));

      Ouput:

      8.72000000000000E+0001
      8.72200000000000E+0001
      8.72200000000000E+0001
      8.72300000000000E+0001
      8.72500000000000E+0001
      8.72500000000000E+0001
      8.72700000000000E+0001
      8.72800000000000E+0001
      8.72800000000000E+0001
      8.73000000000000E+0001

      How does that fit into your banker’s rounding theory?

    • Erik Knowles said

      I think we’re all familiar with Gaussian rounding. However, this particular result is not the result of said rounding mode; in fact, why don’t you take your own advice and read the top of *this* webpage? Specifically: 8.285 as a double is: “87.28499 99999 99996 58939 48683 51519 10781 86035 15625”. Banker’s rounding doesn’t apply.

  4. Slightly disappointing to note that this long established fact of floating points on current hardware STILL keeps cropping up.

    Just WHAT are they teaching in computer science class these days ?!? :)

    On a side note, the post title was confusing to me… it read to me as “RoundTo(87.285, -2) equals some value greater than 87.28” (the more mathematically ‘correct’ reading of the => symbol as “implies” just didn’t make *any* sense) … why not just write it as “=” (equals), which is actually the point. … It was just a tad confusing, tho no real biggy.

    :)

  5. Erik Knowles said

    Second the “What every computer scientist should know…” endorsement. It takes quite a bit of digesting to get past the author’s tendency towards handwaving, but it’s well worth the study.

  6. Louis Kleiman said

    Just a minor issue — currency isn’t a “base 10 number type”. As I understand it, a currency value is an integer type internally, but the decimal point is shifted 4 places for you. And I believe that RoundTo() uses “banker’s rounding” which means round to the nearest even number when a 5 is encountered.

    • jpluimers said

      You are right; Currency is accurate till 4 decimals because it has an implied decimal point.

      Rounding depends on how you setup the floating point unit using the control word.
      Rudy Velthuis has some excellent articles on that: http://rvelthuis.de/articles/articles-floats.html

      –jeroen

    • David Heffernan said

      The scaling by a power of 10 makes Currency behave as a decimal rather than a binary type. For example the number in question, 87.285 is exactly representable in Currency, but not in base-2 floating point. That was the point I was trying to make.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: