[Python-Dev] Floor division (original) (raw)

Tim Peters tim.peters at gmail.com
Sat Jan 20 02:33:23 CET 2007


[Raymond Hettinger]

I bumped into an oddity today:

6.0 // 0.001 != math.floor(6.0 / 0.001) In looking at Objects/floatobject.c, I was surprised to find that floatfloordivision() is implemented in terms of floatdivmod(). Does anyone know why it takes such a circuitous path? I had expected something simpler and faster: return PyFloatFromDouble(floor(a/b));

To preserve, so far as is possible with floats, that

(*) a == (a//b)*b + a%b

In this case the binary double closest to decimal 0.001 is

0.001000000000000000020816681711721685132943093776702880859375

which is a little bit larger than 1/1000. Therefore the mathematical value of a/b is a little bit smaller than 6/(1/1000) = 6000, and the true floor of the mathematically correct result is 5999.

a % b is always computed exactly (mathematical result == machine result) under Python's definition whenever a and b have the same sign (under C99's and the decimal module's definition it's always exact regardless of signs), and getting the exact value for a%b implies the exact value for a//b must also be returned (when possible) in order to preserve identity (*) above.

IOW, since

6.0 % 0.001 0.00099999999999987512

it would be inconsistent to return 6000 for 6.0 // 0.001:

6.0 - 6000 * 0.001 # this isn't close to the value of a%b 0.0 6.0 - 5999 * 0.001 # but this is close 0.00099999999999944578

Note that two rounding errors occur in computing a - N*b via binary doubles. If there were no rounding errors, we'd have

6 % b ==  6.0 - 5999 * b

exactly where

b = 0.001000000000000000020816681711721685132943093776702880859375

is the value actually stored in the machine for 0.001:

import decimal decimal.getcontext().prec = 1000 # way more than needed import math # Set b to exact decimal value of binary double closest to 0.001. m, e = math.frexp(0.001) b = decimal.Decimal(int(m*2**53)) / decimal.Decimal(1 << (53-e)) # Then 6%b is exactly equal to 6 - 5999*b 6 % b == 6 - 5999*b True # Confirm that all decimal calculations were exact. decimal.getcontext().flags[decimal.Inexact] False # Confirm that floor(6/b) is 5999. int(6/b) 5999 print 6/b 5999.99999999999987509990972966989180234686226...

All that said, (*) doesn't make a lot of sense for floats to begin with (for that matter, Python's definition of mod alone doesn't make much sense for floats when the signs differ -- e.g.,

-1 % 1e100 1e+100 decimal.Decimal(-1) % decimal.Decimal("1e100") Decimal("-1")

).



More information about the Python-Dev mailing list