Prev: Design questions for C++ support for Python extensions (cppy)
Next: Python conference and user group statistics
From: Robin Becker on 12 Jul 2010 05:52 A client wants to know why his db number -9.85 gets displayed by some simple code as -9.8 I looked at the number and see that >>> -9.85 -9.8499999999999996 ie I expect simple rounding to produce the observed result and indeed >>> '%.1f' % -9.85 '-9.8' however, when I use round I get an unexpected result ie >>> round(-9.85,1) -9.9000000000000004 according to its definition > round(x[, n])� > Return the floating point value x rounded to n digits after the decimal point. > If n is omitted, it defaults to zero. The result is a floating point number. > Values are rounded to the closest multiple of 10 to the power minus n; > if two multiples are equally close, rounding is done away from 0 (so. for example, > round(0.5) is 1.0 and round(-0.5) is -1.0). so looking at the absolute differences I see >>> abs(-9.9 - -9.85) 0.050000000000000711 >>> abs(-9.8 - -9.85) 0.049999999999998934 ie the -9.8 value appears closer and at least to a primitive test >>> abs(-9.9 - -9.85) > abs(-9.8 - -9.85) True the distance from the -9.9 result is larger, however, that may be because the model numbers for -9.8 & -9.9 differ in distance from the true 10**-n values eg >>> -9.9 -9.9000000000000004 >>> -9.8 -9.8000000000000007 What value should round(-9.85,1) return? Is the result explainable in python (ie without resort to the internal FP representations etc etc)? -- Robin Becker
From: Mark Dickinson on 12 Jul 2010 06:44 On Jul 12, 10:52 am, Robin Becker <ro...(a)reportlab.com> wrote: > What value should round(-9.85,1) return? Is the result explainable in python (ie > without resort to the internal FP representations etc etc)? As you observe, the closest float to -9.85 is actually just a little smaller (i.e., closer to 0) than -9.85: Python 2.7 (r27:82500, Jul 11 2010, 22:38:53) [GCC 4.2.1 (Apple Inc. build 5659)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import decimal >>> decimal.Decimal(-9.85) Decimal('-9.8499999999999996447286321199499070644378662109375') So you're right: round(-9.85, 1) *should* return -9.8. The 2.x (for x <= 6) version of round is a bit deficient in that respect. Internally, it's doing the obvious thing: namely, multiplying by 10.0, rounding to the nearest integer, then dividing by 10.0. The problem is that this not-quite-9.85 value, when multiplied by 10, becomes (as a result of rounding error) *exactly* 98.5, which then gets rounded *up* to 99 instead of down to 98. This is fixed in Python 2.7, and in Python 3.x. (The code that was introduced for the new short float repr made it easy to fix.) That said, if your client really *means* -9.85 (rather than some binary approximation to it), and wants it to round in a predictable manner, the right way to fix this would be to use Decimal instead of float to represent the number. That way, you can also specify what rounding mode you want (instead of relying on the default round-half- away-from-zero in 2.x or round-half-to-even in 3.x.) >>> decimal.Decimal('-9.85').quantize(decimal.Decimal('0.1'), rounding=decimal.ROUND_HALF_UP) Decimal('-9.9') >>> decimal.Decimal('-9.85').quantize(decimal.Decimal('0.1'), rounding=decimal.ROUND_HALF_EVEN) Decimal('-9.8') -- Mark
From: Gary Herron on 12 Jul 2010 12:42 On 07/12/2010 02:52 AM, Robin Becker wrote: > A client wants to know why his db number -9.85 gets displayed by some > simple code as -9.8 > > I looked at the number and see that > > >>> -9.85 > -9.8499999999999996 > > ie I expect simple rounding to produce the observed result and indeed > > >>> '%.1f' % -9.85 > '-9.8' > > however, when I use round I get an unexpected result ie > >>> round(-9.85,1) > -9.9000000000000004 > > according to its definition > >> round(x[, n])¶ >> Return the floating point value x rounded to n digits after the >> decimal point. >> If n is omitted, it defaults to zero. The result is a floating >> point number. >> Values are rounded to the closest multiple of 10 to the power >> minus n; >> if two multiples are equally close, rounding is done away from 0 >> (so. for example, >> round(0.5) is 1.0 and round(-0.5) is -1.0). > > so looking at the absolute differences I see > > >>> abs(-9.9 - -9.85) > 0.050000000000000711 > >>> abs(-9.8 - -9.85) > 0.049999999999998934 Do you *seriously* need to ask a computer if -9.85 is half-way between -9.9 and -9.8. Just look at the numbers man! Of course it's equally close to both, so rounding to -9.9 is correct according to the definition. Do you actually *believe* that -9.9 - -9.85 = 0.050000000000000711. Of course you know it's really 0.05. All you've done here is demonstrate the fallible nature of (the last several digits of) floating point arithmetic on a computer. Don't let that distract you from using your common sense. Gary Herron > ie the -9.8 value appears closer and at least to a primitive test > > >>> abs(-9.9 - -9.85) > abs(-9.8 - -9.85) > True > > the distance from the -9.9 result is larger, however, that may be > because the model numbers for -9.8 & -9.9 differ in distance from the > true 10**-n values eg > > >>> -9.9 > -9.9000000000000004 > >>> -9.8 > -9.8000000000000007 > > What value should round(-9.85,1) return? Is the result explainable in > python (ie without resort to the internal FP representations etc etc)?
From: Emile van Sebille on 12 Jul 2010 14:15 On 7/12/2010 2:52 AM Robin Becker said... <snip> > What value should round(-9.85,1) return? Per round's definition, -9.9. String interpolation for %n.mf doesn't appear to define it's rounding behavior, so a peek at the source would answer what's being done. It does look inconsistent however, and it seems to me rounding and interpolation should behave similarly. So I'd call it a bug. Emile >>> def display(val): .... print "%.1f" % val .... print round(val,1) .... >>> display(1.45) 1.5 1.5 >>> display(-1.45) -1.5 -1.5 >>> display(-1.85) -1.9 -1.9 >>> display(1.85) 1.9 1.9 >>> display(-7.85) -7.8 -7.9 >>> display(-9.85) -9.8 -9.9 >>>
From: Mark Dickinson on 12 Jul 2010 14:59
Emile van Sebille <emile <at> fenx.com> writes: > > On 7/12/2010 2:52 AM Robin Becker said... > <snip> > > What value should round(-9.85,1) return? > > Per round's definition, -9.9. No. The float that's represented by the literal '-9.85' *isn't* exactly -9.85, for all the usual binary floating-point reasons. The value that gets stored is a tiny amount smaller (i.e., closer to zero) than -9.85, so according to the definition it should round *down*, to -9.8. (Or rather, to a float that's very close to, but not exactly equal to, -9.8.) > String interpolation for %n.mf doesn't > appear to define it's rounding behavior, so a peek at the source would > answer what's being done. In Python 2.6, the string interpolation delegates to the system, so it does whatever C string formatting does. Usually that's rounding to nearest, with exact halfway cases rounded to even. In Python 2.7 and 3.x, Python has its own code for string formatting, and again it rounds to nearest, rounding ties to even. > It does look inconsistent however, and it seems to me rounding and > interpolation should behave similarly. Agreed. In this case it's a minor bug that round(-9.85, 1) produces -9.9 instead of -9.8; both string formatting and round should give -9.8. This bug is fixed in Python 2.7 and in Python 3.x. Note that in 2.7 there's still a legitimate difference: round rounds halfway cases away from 0, while string formatting rounds them to even. So the following results are correct: Python 2.7 (r27:82500, Jul 11 2010, 22:38:53) [GCC 4.2.1 (Apple Inc. build 5659)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> round(1.25, 1) 1.3 >>> '%.1f' % 1.25 '1.2' (1.25 *is* an exact halfway case, since it's exactly representable as a binary float.) In Python 3.x, round always does round-half-to-even, so string formatting and round should agree (and if they don't, it's definitely a bug: please report it!) With all this said, asking for *decimal* rounding of *binary* approximations to *decimal* halfway cases to give the results you expect is ... optimistic, to say the least. Use the decimal module if you care about which way your (almost) halfway cases get rounded. [I already replied to this earlier through Google groups, but I'm not sure whether it went through properly. Apologies for the duplication, if so.] -- Mark |