mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
round(x, ndigits) ran ndigits through __index__ before calling __round__, so a user-defined __round__ never saw a non-integer ndigits and an __index__ object reached it already converted to int. CPython's builtin_round_impl() forwards ndigits as-is and leaves the conversion to float.__round__ and int.__round__. Forward the object, and take ndigits as ArgIndex in both slots so they apply __index__ themselves. Calling the slots directly with an __index__ object now works as in CPython instead of raising TypeError. Assisted-by: Claude Code:claude-opus-5 Assisted-by: Claude Code:claude-opus-5-5
143 lines
3.9 KiB
Python
143 lines
3.9 KiB
Python
import math
|
|
|
|
from testutils import assert_raises
|
|
|
|
assert round(1.2) == 1
|
|
assert round(1.8) == 2
|
|
assert round(0.5) == 0
|
|
assert round(1.5) == 2
|
|
assert round(-0.5) == 0
|
|
assert round(-1.5) == -2
|
|
|
|
# ValueError: cannot convert float NaN to integer
|
|
assert_raises(ValueError, round, float("nan"))
|
|
# OverflowError: cannot convert float infinity to integer
|
|
assert_raises(OverflowError, round, float("inf"))
|
|
# OverflowError: cannot convert float infinity to integer
|
|
assert_raises(OverflowError, round, -float("inf"))
|
|
|
|
assert round(0) == 0
|
|
assert isinstance(round(0), int)
|
|
assert round(0.0) == 0
|
|
assert isinstance(round(0.0), int)
|
|
|
|
assert round(0, None) == 0
|
|
assert isinstance(round(0, None), int)
|
|
assert round(0.0, None) == 0
|
|
assert isinstance(round(0, None), int)
|
|
|
|
assert round(0, 0) == 0
|
|
assert isinstance(round(0, 0), int)
|
|
assert round(0.0, 0) == 0.0 # Cannot check the type
|
|
assert isinstance(round(0.0, 0), float)
|
|
|
|
with assert_raises(TypeError):
|
|
round(0, 0.0)
|
|
with assert_raises(TypeError):
|
|
round(0.0, 0.0)
|
|
|
|
|
|
class X:
|
|
def __round__(self, ndigits=None):
|
|
return 1.1
|
|
|
|
|
|
assert round(X(), 1) == 1.1
|
|
assert round(X(), None) == 1.1
|
|
assert round(X()) == 1.1
|
|
|
|
|
|
# Banker's rounding at the decimal level: values like 2.675 store as
|
|
# 2.67499... in IEEE 754, so a multiply-then-round implementation creates
|
|
# a phantom 267.5 tie that round-half-to-even snaps up to 2.68. CPython's
|
|
# `_Py_dg_dtoa` rounds at the decimal level and returns 2.67.
|
|
assert round(2.675, 2) == 2.67
|
|
assert round(2.685, 2) == 2.69
|
|
assert round(-2.675, 2) == -2.67
|
|
assert round(0.05, 1) == 0.1
|
|
assert round(0.15, 1) == 0.1
|
|
assert round(0.35, 1) == 0.3
|
|
assert round(0.45, 1) == 0.5
|
|
assert round(0.65, 1) == 0.7
|
|
assert round(0.85, 1) == 0.8
|
|
assert round(0.95, 1) == 0.9
|
|
assert round(0.645, 2) == 0.65
|
|
assert round(0.665, 2) == 0.67
|
|
assert round(0.685, 2) == 0.69
|
|
assert round(0.695, 2) == 0.69
|
|
assert round(1.685, 2) == 1.69
|
|
assert round(3.745, 2) == 3.75
|
|
|
|
# Exact-halfway ties at integer level use banker's (round-half-to-even).
|
|
assert round(0.5, 0) == 0.0
|
|
assert round(1.5, 0) == 2.0
|
|
assert round(2.5, 0) == 2.0
|
|
|
|
# Negative ndigits uses divide-then-round; banker's still applies.
|
|
assert round(1235, -1) == 1240
|
|
assert round(1245, -1) == 1240
|
|
assert round(1234.5, -1) == 1230.0
|
|
assert round(150, -2) == 200
|
|
assert round(250, -2) == 200
|
|
|
|
# NaN and infinities round to themselves with no error when ndigits is given.
|
|
assert math.isnan(round(float("nan"), 2))
|
|
assert round(float("inf"), 2) == float("inf")
|
|
assert round(float("-inf"), 2) == float("-inf")
|
|
|
|
# Signed zero is preserved through rounding.
|
|
assert math.copysign(1.0, round(-0.0, 2)) == -1.0
|
|
assert math.copysign(1.0, round(0.0, 2)) == 1.0
|
|
|
|
# Out-of-range ndigits short-circuits without overflow.
|
|
assert round(1.0, 1000) == 1.0
|
|
assert round(1.0, -1000) == 0.0
|
|
assert round(1.7976931348623157e308, 0) == 1.7976931348623157e308
|
|
|
|
|
|
# round() normalizes an int subclass to an exact int, like CPython's long_long().
|
|
assert round(True) == 1
|
|
assert type(round(True)) is int
|
|
assert type(round(True, 0)) is int
|
|
assert type(round(False)) is int
|
|
|
|
|
|
class MyInt(int):
|
|
pass
|
|
|
|
|
|
assert round(MyInt(5)) == 5
|
|
assert type(round(MyInt(5))) is int
|
|
assert type(round(MyInt(5), 2)) is int
|
|
# A negative ndigits already produced a fresh exact int.
|
|
assert type(round(MyInt(15), -1)) is int
|
|
|
|
|
|
# round() passes ndigits to __round__ unchanged, like CPython's builtin_round_impl().
|
|
class Echo:
|
|
def __round__(self, *args):
|
|
return args
|
|
|
|
|
|
assert round(Echo(), []) == ([],)
|
|
assert round(Echo(), 1.5) == (1.5,)
|
|
assert type(round(Echo(), True)[0]) is bool
|
|
|
|
|
|
class Index:
|
|
def __index__(self):
|
|
return -1
|
|
|
|
|
|
assert type(round(Echo(), Index())[0]) is Index
|
|
|
|
# int.__round__ and float.__round__ apply __index__ to ndigits themselves.
|
|
assert round(15, Index()) == 20
|
|
assert round(1.25, Index()) == 0.0
|
|
assert (15).__round__(Index()) == 20
|
|
assert (1.25).__round__(Index()) == 0.0
|
|
with assert_raises(TypeError):
|
|
(15).__round__(1.0)
|
|
with assert_raises(TypeError):
|
|
(1.25).__round__([])
|