Numpy rounds in a different way than python
float.__round__
takes special care to produce correctly-rounded results, using a correctly-rounded double-to-string algorithm.
NumPy does not. The NumPy docs mention that
Results may also be surprising due to the inexact representation of decimal fractions in the IEEE floating point standard [R9] and errors introduced when scaling by powers of ten.
This is faster, but produces more rounding error. It leads to errors like what you've observed, as well as errors where numbers even more unambiguously below the cutoff still get rounded up:
>>> x = 0.33499999999999996>>> x0.33499999999999996>>> x < 0.335True>>> x < Decimal('0.335')True>>> x < 0.67/2True>>> round(x, 2)0.33>>> numpy.round(x, 2)0.34000000000000002
You're getting a slower time for NumPy's rounding, but that doesn't have anything to do with which rounding algorithm is slower. Any time comparison between NumPy and regular Python math will boil down to the fact that NumPy is optimized for whole-array operations. Doing math on single NumPy scalars has a lot of overhead, but rounding an entire array with numpy.round
easily beats rounding a list of floats with round
:
In [6]: import numpyIn [7]: l = [i/7 for i in range(100)]In [8]: a = numpy.array(l)In [9]: %timeit [round(x, 1) for x in l]59.6 µs ± 408 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)In [10]: %timeit numpy.round(a, 1)5.27 µs ± 145 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
As for which one is more accurate, that's definitely float.__round__
. Your number is closer to 5.92270987 than to 5.92270988, and it's round-ties-to-even, not round-everything-to-even. There's no tie here.
And yes, yet another way to deal with such things is to use Decimal which is not that slow in python3:
%%timeit d = D('11.84541975'); q = D('0.00000001')(d/2).quantize(q)485 ns ± 10.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
tldr
Builtin round
and numpy.round
use different algorithms for rounding. Their results are identical for most numbers, but substantially different for some corner cases.
Both are good for certain uses.
round
is faster for scalars, np.round
is faster for arrays.
explanation
• Builtin round
uses a straightforward approach of checking the decimal digit right after the requested one. It rounds 4 downwards no matter what happens to be after (even if it is ...499999), and rounds 5 to the nearest even unless it has a single 1 thereafter (eg ...500001), in which case it rounds up.
• np.round
multiplies the number to the requested the power of 10, rounds to the nearest int by ordinary rules, then divides back by the same power of 10.
It gives more predictable results for cases like 0.33/2:
>>> 0.33/20.165>>> round(0.33/2, 2)0.17>>> np.round(0.33/2, 2)0.16
Here 0.165 should've been rounded to the nearest even which is 0.16.
Update:
Yet it suffers from round-off errors for cases like 1.09/2 (as noted by Mark Dickinson in the comment):
>>> 1.09/20.545>>> round(1.09/2, 2)0.55>>> np.round(1.09/2, 2)0.55
The only workaround I could think of is
>>> round(round(1.09*100)/2)/1000.54
which works but is far from being universal.