Files
RustPython/extra_tests/snippets/operator_comparison.py
Jeong, YunWon 9e4c5708c6 str, bytes: answer equality with equality rather than with an ordering (#8531)
PyStr's comparison, PyBytesInner's, and the specialized CompareOpStr
instruction all answered == and != by taking Ord::cmp of the two buffers
and asking whether the result was Equal. An ordering has to read the
bytes: it memcmps the common prefix even where the lengths already settle
the question. CompareOpStr bypasses the Comparable slot, so it had also
lost the identity shortcut that slot takes, and a string compared with
itself was read end to end.

Add PyComparisonOp::eval_eq, which settles Eq and Ne from an equality test
and leaves an ordering operator to the caller, and answer through it in the
three places: slice equality checks the length first, and CompareOpStr
answers an object compared with itself the way the slot it specializes does.

n=1,000,000, per comparison:

                                      before      after
    s == s (the very same object)    23.21us     0.16us
    s == a string one shorter        24.63us     0.17us
    b == bytes one shorter           25.28us     0.20us
    ba == bytearray one shorter      27.68us     0.23us
    s == an equal, distinct string   23.78us    24.50us
    s <  an equal string             29.87us    25.07us

Assisted-by: Claude
2026-08-15 18:23:05 +09:00

137 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from testutils import assert_raises
assert 1 < 2
assert 1 < 2 < 3
assert 5 == 5 == 5
assert (5 == 5) == True
assert 5 == 5 != 4 == 4 > 3 > 2 < 3 <= 3 != 0 == 0
assert not 1 > 2
assert not 5 == 5 == True
assert not 5 == 5 != 5 == 5
assert not 1 < 2 < 3 > 4
assert not 1 < 2 > 3 < 4
assert not 1 > 2 < 3 < 4
def test_type_error(x, y):
assert_raises(TypeError, lambda: x < y)
assert_raises(TypeError, lambda: x <= y)
assert_raises(TypeError, lambda: x > y)
assert_raises(TypeError, lambda: x >= y)
test_type_error([], 0)
test_type_error((), 0)
# 10**308 cannot be represented exactly in f64, thus it is not equal to 1e308 float
assert not (10**308 == 1e308)
# but the 1e308 float can be converted to big int and then it still should be equal to itself
assert int(1e308) == 1e308
# and the equalities should be the same when operands switch sides
assert not (1e308 == 10**308)
assert 1e308 == int(1e308)
# floats that cannot be converted to big ints shouldn’t crash the vm
import math
assert not (10**500 == math.inf)
assert not (math.inf == 10**500)
assert not (10**500 == math.nan)
assert not (math.nan == 10**500)
# comparisons
# floats with worse than integer precision
assert 2.0**54 > 2**54 - 1
assert 2.0**54 < 2**54 + 1
assert 2.0**54 >= 2**54 - 1
assert 2.0**54 <= 2**54 + 1
assert 2.0**54 == 2**54
assert not 2.0**54 == 2**54 + 1
# inverse operands
assert 2**54 - 1 < 2.0**54
assert 2**54 + 1 > 2.0**54
assert 2**54 - 1 <= 2.0**54
assert 2**54 + 1 >= 2.0**54
assert 2**54 == 2.0**54
assert not 2**54 + 1 == 2.0**54
assert not 2.0**54 < 2**54 - 1
assert not 2.0**54 > 2**54 + 1
# sub-int numbers
assert 1.3 > 1
assert 1.3 >= 1
assert 2.5 > 2
assert 2.5 >= 2
assert -0.3 < 0
assert -0.3 <= 0
# int out of float range comparisons
assert 10**500 > 2.0**54
assert -(10**500) < -0.12
# infinity and NaN comparisons
assert math.inf > 10**500
assert math.inf >= 10**500
assert not math.inf < 10**500
assert -math.inf < -10 * 500
assert -math.inf <= -10 * 500
assert not -math.inf > -10 * 500
assert not math.nan > 123
assert not math.nan < 123
assert not math.nan >= 123
assert not math.nan <= 123
# str and bytes comparisons, through a function so that the operands are not
# constants the compiler can fold, and in a loop so the specialized comparison
# is reached.
def cmp_all(a, b):
return (a == b, a != b, a < b, a <= b, a > b, a >= b)
def check(a, b, expected):
for _ in range(200):
assert cmp_all(a, b) == expected, (a, b, cmp_all(a, b), expected)
EQ = (True, False, False, True, False, True)
LT = (False, True, True, True, False, False)
GT = (False, True, False, False, True, True)
same = "abc" * 3
check(same, same, EQ) # the very same object
check(same, "abcabcabc", EQ) # equal, distinct objects
check("abc", "abd", LT) # same length, differing content
check("abc", "abcd", LT) # a prefix is less than what extends it
check("abcd", "abc", GT)
check("", "a", LT)
check("", "", EQ)
check("\ud800", "\ud800", EQ) # lone surrogates are compared as themselves
check("\ud800", "\udfff", LT)
check("a\U0001f600", "a\U0001f600", EQ)
check("가나다", "가나다", EQ)
check("가나", "가나다", LT)
# Comparing with a non-string is never an error for == and !=.
assert not "abc" == 3
assert "abc" != 3
bsame = b"abc" * 3
check(bsame, bsame, EQ)
check(bsame, b"abcabcabc", EQ)
check(b"abc", b"abd", LT)
check(b"abc", b"abcd", LT)
check(b"abcd", b"abc", GT)
check(bytearray(b"abc"), bytearray(b"abcd"), LT)
check(bytearray(b"abc"), b"abc", EQ) # bytearray and bytes compare by content
check(b"abc", bytearray(b"abd"), LT)
assert not b"abc" == "abc"
assert b"abc" != "abc"