Skip to content

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

Merged
merged 21 commits into from
Jul 28, 2018

Conversation

rhettinger
Copy link
Contributor

@rhettinger rhettinger commented Jul 26, 2018

Copy link
Member

@tim-one tim-one left a 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
Copy link
Member

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)?

Copy link
Contributor Author

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)".

# 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)
Copy link
Member

@tim-one tim-one Jul 26, 2018

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.

Copy link
Contributor Author

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.

}
for (i=0 ; i<n ; i++) {
item = PyTuple_GET_ITEM(args, i);
x = PyFloat_AsDouble(item) / max;
Copy link
Member

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.

Copy link
Contributor Author

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.

x = PyFloat_AsDouble(item) / max;
csum += x * x;
}
return PyFloat_FromDouble(max * sqrt(csum)); // XXX Handle overflow
Copy link
Member

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

Copy link
Contributor Author

@rhettinger rhettinger Jul 27, 2018

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants