[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 > float_floor_division() is implemented in terms of float_divmod(). Does anyone > know why it takes such a circuitous path? I had expected something simpler > and > faster: > > return PyFloat_FromDouble(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") ). _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com