-
-
Notifications
You must be signed in to change notification settings - Fork 31.8k
bpo-33089: Multidimensional math.hypot() #8474
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks useful to me! Left some minor comments.
|
||
# Test corner cases | ||
self.assertEqual(hypot(0.0, 0.0), 0.0) # Max input is zero | ||
self.assertEqual(hypot(-10.5), 10.5) # Negative input |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add self.assertEqual(hypot(), 0.0)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, added an explict test for this case. FWIW, this was already tested in the section "Test different numbers of arguments (from zero to five)".
Lib/test/test_math.py
Outdated
# Verify scaling for extremely small values | ||
for exp in range(32): | ||
scale = FLOAT_MIN / 2.0 ** exp | ||
self.assertEqual(math.hypot(12*scale, 5*scale), 13*scale) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me nervous, because it depends on that
12 * math.sqrt(1 + (5/12)**2)
happens to be exactly 13.0 despite that 5/12 isn't exactly representable. Would be happier with:
self.assertEqual(math.hypot(4*scale, 3*scale), 5*scale)
because every intermediate value in
4 * math.sqrt(1 + (3/4)**2)
is exactly representable in binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, changed to a 3-4-5 triangle.
Modules/mathmodule.c
Outdated
} | ||
for (i=0 ; i<n ; i++) { | ||
item = PyTuple_GET_ITEM(args, i); | ||
x = PyFloat_AsDouble(item) / max; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we quite sure a malicious __float__()
implementation can't mutate other values in the argument tuple? That is, that no conversion failed the first time through the tuple guarantees none will fail the second time through?
I don't know. In which case checking for a -1.0 error return here too would be safest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, added the -1.0 test.
Modules/mathmodule.c
Outdated
x = PyFloat_AsDouble(item) / max; | ||
csum += x * x; | ||
} | ||
return PyFloat_FromDouble(max * sqrt(csum)); // XXX Handle overflow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's good enough for me that +Inf will result from overflow here, but that it is a change from, e.g., what 3.7 does on Windows:
>>> import sys
>>> import math
>>> x = sys.float_info.max
>>> math.hypot(x, x)
Traceback (most recent call last):
...
OverflowError: math range error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is good enough for me too :-) I like that it matches the behavior of the pure python code:
>>> import sys
>>> coordinates = [sys.float_info.max] * 1
>>> scale * sqrt(sum((x/scale) ** x for x in coordinates))
1.7976931348623157e+308
>>> coordinates = [sys.float_info.max] * 2
>>> scale * sqrt(sum((x/scale) ** x for x in coordinates))
inf
* Made test for the zero argument case explicit. * Use exactly representable numbers in the extreme small value tests. * Guard against a malicious __float__ that succeeds on the first call and fails on the second.
https://bugs.python.org/issue33089