> Mark, I tried `Fraction(10**23) // 1e22`, and I got 10.
Sorry: that result was with your PR (as it was at the time I wrote that comment). On master, you do indeed get 10.
> I think the fact that floating-point rounding error sometimes causes
> strange results is not a reason to do really unexpected things like
> making 1.0 // 1/10 equal 9.0, if that can be reasonably avoided.
I understand, but I think in this case, the cure is worse than the disease. I don't think converting the float to a Fraction in mixed-type operations is going to work, for a number of reasons:
- it's changing the behaviour for _all_ the arithmetic operations, not just // and %; that widens the scope for accidental breakage. For example, with the latest commit 038664e6a6288f6113ab96103e57d3b25b39a8c2 on your PR:
>>> Fraction(1) + math.inf
inf
>>> math.inf + Fraction(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/mdickinson/Python/cpython/Lib/fractions.py", line 391, in reverse
return float(fallback_operator(Fraction(a), b))
File "/Users/mdickinson/Python/cpython/Lib/fractions.py", line 130, in __new__
self._numerator, self._denominator = numerator.as_integer_ratio()
OverflowError: cannot convert Infinity to integer ratio
But on master, we have:
>>> import math
>>> from fractions import Fraction
>>> math.inf + Fraction(1)
inf
>>> Fraction(1) + math.inf
inf
- it's counter to the spirit of the numeric tower, where for most arithmetic operations, values are propagated _up_ the tower (from Integral -> Rational -> Real -> Complex) to the first common type before the operation is performed. That keeps things simple; having some cases where values are converted down the tower is going to cause confusion. (Comparisons are necessarily a special case: the need to preserve transitivity of equality and trichotomy means that using the same rules as for arithmetic operations won't fly)
- it introduces a non-obvious difference between mixed-mode Fraction-float and int-float operations. For an int x and a float y, I'd expect the result of `x <some_op> y` to be identical to the result of `Fraction(x) <some_op> y`.
So I'm sorry, but I do think having 1.0 // Fraction(1, 10) give 9.0 rather than 10.0 is the least worst option here. It's a surprise, but it's not a _new_ surprise: it's a surprise that's already there in the world of floating-point arithmetic.
>>> 1.0 // 0.1
9.0
You're evaluating a discontinuous function _at_ a discontinuity, using a type that's known for its inexactness. In that situation, getting the value for _either_ side of that discontinuity is reasonable. |