I have a Python 3 function rounding floats to 6 digits (the logic handles various precision levels). When passed with many (possibly all) numpy.float16 values it generates multiply-overflow warnings and returns infinity.
The short snippet in the question title or shown below illustrates the behavior.
The workaround is easy, just convert to larger floats first, but I'm curious whether the behavior is expected.
import numpy as np
x = np.float16(3.14)
x = round(x, 5)
if np.isinf(x):
print("you've made an infinity through rounding....", 1, x)
else:
print('just x: ', x)
I expect round to affect precision, but never cause an overflow or change a value into infinity.
Surprisingly, that is not how rounding works in Python. Rounding half numbers does not round up, and in fact, it doesn't always round down either. Instead, it rounds to the nearest even number. It is worth pointing out that besides the half-number case, round() works as expected, in that it returns the nearest integer.
5 is round up for positive values and round down for negative values. For instance, both round(0.5) and round(-0.5) return 0 , while round(1.5) gives 2 and round(-1.5) gives -2 . This Python behaviour is a bit different from how rounding usually goes.
It is simply always showing a fixed number of significant digits. Try import math; p=3.14; print p; p=math. pi; print p .
Python’s decimal module can also be used for representing infinite float values. It is used as Decimal (‘Infinity’) for positive and Decimal (‘-Infinity’) for negative infinite value. The below code shows its implementation: Python’s Numpy module can also be used for representing infinite values.
It has nothing to do with Python. The error has to do with how machines store floating-point numbers in memory. Most modern computers store floating-point numbers as binary decimals with 53-bit precision. Only numbers that have finite binary decimal representations that can be expressed in 53 bits are stored as an exact value.
One can use float (‘inf’) as an integer to represent it as infinity. Below is the list of ways one can represent infinity in Python. 1. Using float (‘inf’) and float (‘-inf’):
That is more digits than most people find useful, so Python keeps the number of digits manageable by displaying a rounded value instead Just remember, even though the printed result looks like the exact value of 1/10, the actual stored value is the nearest representable binary fraction.
This is a limitation of NumPy's round
algorithm. I hesitate to call it a bug: that's for the NumPy core developers to decide, but it may be worth reporting nevertheless.
Here's the problem: to round to 5 decimal places, NumPy does the equivalent of scaling by 100000.0
, rounding to the nearest integer, then diving by 100000.0
again. That initial scaling can potentially overflow, even when the final result of the round
operation would be expected to be within range.
Here's the portion of the NumPy source where this is implemented. You need to backtrack a bit in the source to figure out that in this case op1
is multiplication and op2
refers to division.
With float64
or float32
this would be unlikely to cause issues, because for normal uses you're unlikely to be within 100000.0
of the upper bound of the representable range of the floating-point type. But if you do get too close to that upper bound, you'll see the same problem. Here's an example with np.float64
:
>>> np.finfo(np.float64).max
1.7976931348623157e+308
>>> x = np.float64(1e304) # pick something within 1e5 of that max
>>> x * 1e5
__main__:1: RuntimeWarning: overflow encountered in double_scalars
inf
>>> np.round(x, 5) # same multiplication happening internally
/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numpy/core/fromnumeric.py:56: RuntimeWarning: overflow encountered in multiply
return getattr(obj, method)(*args, **kwds)
inf
And here's the same thing with float32
:
>>> np.finfo(np.float32).max
3.4028235e+38
>>> x = np.float32(1e35)
>>> x * 1e5 # okay; NumPy converts to `float64`
1.0000000409184788e+40
>>> np.round(x, 5)
inf
With np.float16
, it's exactly the same issue, but since the dynamic range of the float16
type is so small, you're much more likely to observe the issue in practice.
In general, though, note that even if this were fixed, it is possible for two-argument round
to overflow: it's possible that the original value is within the range of the relevant floating-point type, while the rounded value is not. Here's an example with Python's own round
function:
>>> x = 1.76e308
>>> x
1.76e+308
>>> round(x, -307) # should be 1.8e308, but that's out of range
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: rounded value too large to represent
But this can only happen for a negative ndigits
argument. If the second argument were nonnegative, then there's no possibility of overflow - every sufficiently large representable value in any of the standard floating-point types is already integral, so a round
with a nonnegative ndigits
shouldn't change its value.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With