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.
Delphi Multiplikationen/divisionen optimieren - Delphi-PRAXiS said
[…] […]
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?
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.
Jolyon Smith said
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.
:)
Erik Knowles said
Jolyon,
For a little stomach-churning reading, check out this CodeProject thread, which made their daily e-mail:
http://www.codeproject.com/Lounge.aspx?fid=1159&select=3652631&tid=3652631
One would hope the thread participants are spending their time on outsourced “press-Enter-to-continue” projects, *not* medical device control software or somesuch.
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.
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.