diff options
author | Tim Peters <tim.peters@gmail.com> | 2004-09-23 08:06:40 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2004-09-23 08:06:40 (GMT) |
commit | 307fa78107c39ffda1eb4ad18201d25650354c4e (patch) | |
tree | 6b2daf6dc3169cfc2054ebc4162647b5c647d875 /Lib | |
parent | 4533f1fb7fbf1fa8a9cb264ff6f1f0aba043e80d (diff) | |
download | cpython-307fa78107c39ffda1eb4ad18201d25650354c4e.zip cpython-307fa78107c39ffda1eb4ad18201d25650354c4e.tar.gz cpython-307fa78107c39ffda1eb4ad18201d25650354c4e.tar.bz2 |
SF bug #513866: Float/long comparison anomaly.
When an integer is compared to a float now, the int isn't coerced to float.
This avoids spurious overflow exceptions and insane results. This should
compute correct results, without raising spurious exceptions, in all cases
now -- although I expect that what happens when an int/long is compared to
a NaN is still a platform accident.
Note that we had potential problems here even with "short" ints, on boxes
where sizeof(long)==8. There's #ifdef'ed code here to handle that, but
I can't test it as intended. I tested it by changing the #ifdef to
trigger on my 32-bit box instead.
I suppose this is a bugfix candidate, but I won't backport it. It's
long-winded (for speed) and messy (because the problem is messy). Note
that this also depends on a previous 2.4 patch that introduced
_Py_SwappedOp[] as an extern.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_long.py | 104 |
1 files changed, 102 insertions, 2 deletions
diff --git a/Lib/test/test_long.py b/Lib/test/test_long.py index 1a04ce9..74ae7c6 100644 --- a/Lib/test/test_long.py +++ b/Lib/test/test_long.py @@ -387,8 +387,7 @@ def test_float_overflow(): "1. ** huge", "huge ** 1.", "1. ** mhuge", "mhuge ** 1.", "math.sin(huge)", "math.sin(mhuge)", "math.sqrt(huge)", "math.sqrt(mhuge)", # should do better - "math.floor(huge)", "math.floor(mhuge)", - "float(shuge) == int(shuge)"]: + "math.floor(huge)", "math.floor(mhuge)"]: try: eval(test, namespace) @@ -397,6 +396,11 @@ def test_float_overflow(): else: raise TestFailed("expected OverflowError from %s" % test) + # XXX Perhaps float(shuge) can raise OverflowError on some box? + # The comparison should not. + if float(shuge) == int(shuge): + raise TestFailed("float(shuge) should not equal int(shuge)") + # ---------------------------------------------- test huge log and log10 def test_logs(): @@ -431,6 +435,101 @@ def test_logs(): except ValueError: pass +# ----------------------------------------------- test mixed comparisons + +def test_mixed_compares(): + import math + import sys + + if verbose: + print "mixed comparisons" + + # We're mostly concerned with that mixing floats and longs does the + # right stuff, even when longs are too large to fit in a float. + # The safest way to check the results is to use an entirely different + # method, which we do here via a skeletal rational class (which + # represents all Python ints, longs and floats exactly). + class Rat: + def __init__(self, value): + if isinstance(value, (int, long)): + self.n = value + self.d = 1 + + elif isinstance(value, float): + # Convert to exact rational equivalent. + f, e = math.frexp(abs(value)) + assert f == 0 or 0.5 <= f < 1.0 + # |value| = f * 2**e exactly + + # Suck up CHUNK bits at a time; 28 is enough so that we suck + # up all bits in 2 iterations for all known binary double- + # precision formats, and small enough to fit in an int. + CHUNK = 28 + top = 0 + # invariant: |value| = (top + f) * 2**e exactly + while f: + f = math.ldexp(f, CHUNK) + digit = int(f) + assert digit >> CHUNK == 0 + top = (top << CHUNK) | digit + f -= digit + assert 0.0 <= f < 1.0 + e -= CHUNK + + # Now |value| = top * 2**e exactly. + if e >= 0: + n = top << e + d = 1 + else: + n = top + d = 1 << -e + if value < 0: + n = -n + self.n = n + self.d = d + assert float(n) / float(d) == value + + else: + raise TypeError("can't deal with %r" % val) + + def __cmp__(self, other): + if not isinstance(other, Rat): + other = Rat(other) + return cmp(self.n * other.d, self.d * other.n) + + cases = [0, 0.001, 0.99, 1.0, 1.5, 1e20, 1e200] + # 2**48 is an important boundary in the internals. 2**53 is an + # important boundary for IEEE double precision. + for t in 2.0**48, 2.0**50, 2.0**53: + cases.extend([t - 1.0, t - 0.3, t, t + 0.3, t + 1.0, + long(t-1), long(t), long(t+1)]) + cases.extend([0, 1, 2, sys.maxint, float(sys.maxint)]) + # 1L<<20000 should exceed all double formats. long(1e200) is to + # check that we get equality with 1e200 above. + t = long(1e200) + cases.extend([0L, 1L, 2L, 1L << 20000, t-1, t, t+1]) + cases.extend([-x for x in cases]) + for x in cases: + Rx = Rat(x) + for y in cases: + Ry = Rat(y) + Rcmp = cmp(Rx, Ry) + xycmp = cmp(x, y) + if Rcmp != xycmp: + raise TestFailed('%r %r %d %d' % (x, y, Rcmp, xycmp)) + if (x == y) != (Rcmp == 0): + raise TestFailed('%r == %r %d' % (x, y, Rcmp)) + if (x != y) != (Rcmp != 0): + raise TestFailed('%r != %r %d' % (x, y, Rcmp)) + if (x < y) != (Rcmp < 0): + raise TestFailed('%r < %r %d' % (x, y, Rcmp)) + if (x <= y) != (Rcmp <= 0): + raise TestFailed('%r <= %r %d' % (x, y, Rcmp)) + if (x > y) != (Rcmp > 0): + raise TestFailed('%r > %r %d' % (x, y, Rcmp)) + if (x >= y) != (Rcmp >= 0): + raise TestFailed('%r >= %r %d' % (x, y, Rcmp)) + # ---------------------------------------------------------------- do it test_division() @@ -441,3 +540,4 @@ test_misc() test_auto_overflow() test_float_overflow() test_logs() +test_mixed_compares() |