Skip to content

Commit b1bd06a

Browse files
committed
gh-NNNN: Add regression tests for round(x, k) with negative k and large x
Add RoundTestCase.test_round_neg_ndigits_large to Lib/test/test_float.py. These 20 cases expose the failure mode of the pure-arithmetic fallback for float.__round__ with negative ndigits: dividing a large float by 10^(-k) loses ULPs, pushing a value that is already a precise multiple of 10^k to the adjacent float, causing the round-trip to return the wrong neighbour. Each test value is already an exact multiple of 10^k within float precision, so round(x, k) must return x unchanged. The arithmetic fallback (x / 10^k, round, * 10^k) returns the adjacent float instead, 1 ULP off from the correct answer. The test is decorated with @skipUnless float_repr_style == 'short' because the arithmetic fallback is the only implementation on platforms without a correctly-rounded decimal conversion (_PY_SHORT_FLOAT_REPR==0), and those platforms are known to have this limitation. Cases were found by random search comparing the dtoa string round-trip against the arithmetic fallback across 500 000 random large floats.
1 parent 8ff0b7d commit b1bd06a

File tree

1 file changed

+40
-0
lines changed

1 file changed

+40
-0
lines changed

Lib/test/test_float.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,46 @@ def test_overflow(self):
962962
self.assertRaises(OverflowError, round, 1.6e308, -308)
963963
self.assertRaises(OverflowError, round, -1.7e308, -308)
964964

965+
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
966+
"requires correctly-rounded decimal conversion")
967+
def test_round_neg_ndigits_large(self):
968+
# round(x, k) for negative k and large x requires a correctly-rounded
969+
# decimal round-trip (Gay's dtoa or equivalent). A pure-arithmetic
970+
# fallback (x / 10**-k, round, * 10**-k) loses ULPs when x is large,
971+
# producing results that are off by 1 ULP from the correct answer.
972+
#
973+
# These specific cases were found by comparing the dtoa string round-trip
974+
# against the arithmetic fallback across 500 000 random large floats.
975+
# Each value is its own correctly-rounded result (i.e. it is already a
976+
# multiple of 10**-k to within float precision), so round() must return
977+
# it unchanged. The arithmetic path returns the adjacent float instead.
978+
cases = [
979+
# (x, ndigits) — round(x, ndigits) must equal x
980+
(1e200, -190),
981+
(-5.149362796701729e+153, -8),
982+
(2.3674206222080845e+156, -4),
983+
(-1.2961718851440568e+225, -8),
984+
(-6.818581056885784e+97, -2),
985+
(6.79255997546879e+152, -8),
986+
(-6.328101478167142e+265, -9),
987+
(1.4306958843609618e+234, -2),
988+
(1.0367390072764618e+96, -8),
989+
(-1.6336871169955384e+252, -1),
990+
(7.029495037016018e+124, -1),
991+
(-4.6340895967464987e+80, -1),
992+
(1.0653282223232621e+189, -6),
993+
(1.7885118156748245e+148, -4),
994+
(9.362247525287383e+253, -7),
995+
(6.955704913061252e+227, -4),
996+
(-7.239529999179169e+68, -2),
997+
(-1.2399047534739918e+86, -6),
998+
(9.351066400911148e+58, -4),
999+
(1.609493193540256e+139, -4),
1000+
]
1001+
for x, k in cases:
1002+
with self.subTest(x=x, k=k):
1003+
self.assertEqual(round(x, k), x)
1004+
9651005
@unittest.skipUnless(getattr(sys, 'float_repr_style', '') == 'short',
9661006
"applies only when using short float repr style")
9671007
def test_previous_round_bugs(self):

0 commit comments

Comments
 (0)