Skip to content

bpo-41513: Second attempt to add accuracy tests for math.hypot() #22327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 21, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,69 @@ def testHypot(self):
scale = FLOAT_MIN / 2.0 ** exp
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)

@requires_IEEE_754
@unittest.skipIf(HAVE_DOUBLE_ROUNDING,
"hypot() loses accuracy on machines with double rounding")
def testHypotAccuracy(self):
# Verify improved accuracy in cases that were known to be inaccurate.
#
# The new algorithm's accuracy depends on IEEE 754 arithmetic
# guarantees, on having the usual ROUND HALF EVEN rounding mode, on
# the system not having double rounding due to extended precision,
# and on the compiler maintaining the specified order of operations.
#
# This test is known to succeed on most of our builds. If it fails
# some build, we either need to add another skipIf if the cause is
# identifiable; otherwise, we can remove this test entirely.

hypot = math.hypot
Decimal = decimal.Decimal
high_precision = decimal.Context(prec=500)

for hx, hy in [
# Cases with a 1 ulp error in Python 3.7 compiled with Clang
('0x1.10e89518dca48p+29', '0x1.1970f7565b7efp+30'),
('0x1.10106eb4b44a2p+29', '0x1.ef0596cdc97f8p+29'),
('0x1.459c058e20bb7p+30', '0x1.993ca009b9178p+29'),
('0x1.378371ae67c0cp+30', '0x1.fbe6619854b4cp+29'),
('0x1.f4cd0574fb97ap+29', '0x1.50fe31669340ep+30'),
('0x1.494b2cdd3d446p+29', '0x1.212a5367b4c7cp+29'),
('0x1.f84e649f1e46dp+29', '0x1.1fa56bef8eec4p+30'),
('0x1.2e817edd3d6fap+30', '0x1.eb0814f1e9602p+29'),
('0x1.0d3a6e3d04245p+29', '0x1.32a62fea52352p+30'),
('0x1.888e19611bfc5p+29', '0x1.52b8e70b24353p+29'),

# Cases with 2 ulp error in Python 3.8
('0x1.538816d48a13fp+29', '0x1.7967c5ca43e16p+29'),
('0x1.57b47b7234530p+29', '0x1.74e2c7040e772p+29'),
('0x1.821b685e9b168p+30', '0x1.677dc1c1e3dc6p+29'),
('0x1.9e8247f67097bp+29', '0x1.24bd2dc4f4baep+29'),
('0x1.b73b59e0cb5f9p+29', '0x1.da899ab784a97p+28'),
('0x1.94a8d2842a7cfp+30', '0x1.326a51d4d8d8ap+30'),
('0x1.e930b9cd99035p+29', '0x1.5a1030e18dff9p+30'),
('0x1.1592bbb0e4690p+29', '0x1.a9c337b33fb9ap+29'),
('0x1.1243a50751fd4p+29', '0x1.a5a10175622d9p+29'),
('0x1.57a8596e74722p+30', '0x1.42d1af9d04da9p+30'),

# Cases with 1 ulp error in version fff3c28052e6b0
('0x1.ee7dbd9565899p+29', '0x1.7ab4d6fc6e4b4p+29'),
('0x1.5c6bfbec5c4dcp+30', '0x1.02511184b4970p+30'),
('0x1.59dcebba995cap+30', '0x1.50ca7e7c38854p+29'),
('0x1.768cdd94cf5aap+29', '0x1.9cfdc5571d38ep+29'),
('0x1.dcf137d60262ep+29', '0x1.1101621990b3ep+30'),
('0x1.3a2d006e288b0p+30', '0x1.e9a240914326cp+29'),
('0x1.62a32f7f53c61p+29', '0x1.47eb6cd72684fp+29'),
('0x1.d3bcb60748ef2p+29', '0x1.3f13c4056312cp+30'),
('0x1.282bdb82f17f3p+30', '0x1.640ba4c4eed3ap+30'),
('0x1.89d8c423ea0c6p+29', '0x1.d35dcfe902bc3p+29'),
]:
x = float.fromhex(hx)
y = float.fromhex(hy)
with self.subTest(hx=hx, hy=hy, x=x, y=y):
with decimal.localcontext(high_precision):
z = float((Decimal(x)**2 + Decimal(y)**2).sqrt())
self.assertEqual(hypot(x, y), z)

def testDist(self):
from decimal import Decimal as D
from fractions import Fraction as F
Expand Down