So, this kind of behavior (as noted in comments), is a very traditional form of rounding, seen in the round half to even method. Also known (according to David Heffernan) as banker's rounding. The numpy documentation around this behavior implies that they are using this type of rounding, but also implies that there may be issues with the way in which numpy interacts with the IEEE floating point format. (shown below)

Notes
-----
For values exactly halfway between rounded decimal values, Numpy
rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0,
-0.5 and 0.5 round to 0.0, etc. Results may also be surprising due
to the inexact representation of decimal fractions in the IEEE
floating point standard [1]_ and errors introduced when scaling
by powers of ten.

Whether or not that is the case, I honestly don't know. I do know that large portions of the numpy core are still written in FORTRAN 77, which predates the IEEE standard (set in 1984), but I don't know enough FORTRAN 77 to say whether or not there's some issue with the interface here.

If you're looking to just round up regardless, the np.ceil function (ceiling function in general), will do this. If you're looking for the opposite (always rounding down), the np.floor function will achieve this.

Answer from Slater Victoroff on Stack Overflow
🌐
Programiz
programiz.com › python-programming › numpy › methods › round
NumPy round() (With Examples)
If you prefer to have the output as integers without the decimal points, you can convert the data type of the rounded array to int using astype() as: ... import numpy as np # create an array array1 = np.array([1.23456789, 2.3456789, 3.456789]) # round the elements to 2 decimal places rounded_array ...
🌐
NumPy
numpy.org › doc › 2.2 › reference › generated › numpy.round.html
numpy.round — NumPy v2.2 Manual
Unless out was specified, a new array is created. A reference to the result is returned. The real and imaginary parts of complex numbers are rounded separately.
🌐
Vultr Docs
docs.vultr.com › python › third-party › numpy › round
Python Numpy round() - Round Decimal Numbers | Vultr Docs
November 15, 2024 - Import the NumPy library. Create an array of floating-point numbers. Use the round() function to round these numbers to the nearest integers.
🌐
Note.nkmk.me
note.nkmk.me › home › python › numpy
NumPy: Round array elements (np.round, np.around, np.rint) | note.nkmk.me
January 15, 2024 - For example, if the original array is of type float, the rounded array will also be float, regardless of the number of decimal places. To convert the result to an integer (int), use astype() after np.round().
🌐
Note.nkmk.me
note.nkmk.me › home › python › numpy
NumPy: Round up/down array elements (np.floor, np.trunc, np.ceil) | note.nkmk.me
January 15, 2024 - The returned data type is a floating-point number (float). Use astype() to convert it to an integer (int). This also applies to np.trunc(), np.ceil(), etc. NumPy: Cast ndarray to a specific dtype with astype()
🌐
Codecademy
codecademy.com › docs › python:numpy › ndarray › round()
Python:NumPy | ndarray | round() | Codecademy
October 29, 2025 - The round() method in NumPy rounds each element of an array to the nearest integer or to a specified number of decimal places.
Find elsewhere
Top answer
1 of 9
20

So, this kind of behavior (as noted in comments), is a very traditional form of rounding, seen in the round half to even method. Also known (according to David Heffernan) as banker's rounding. The numpy documentation around this behavior implies that they are using this type of rounding, but also implies that there may be issues with the way in which numpy interacts with the IEEE floating point format. (shown below)

Notes
-----
For values exactly halfway between rounded decimal values, Numpy
rounds to the nearest even value. Thus 1.5 and 2.5 round to 2.0,
-0.5 and 0.5 round to 0.0, etc. Results may also be surprising due
to the inexact representation of decimal fractions in the IEEE
floating point standard [1]_ and errors introduced when scaling
by powers of ten.

Whether or not that is the case, I honestly don't know. I do know that large portions of the numpy core are still written in FORTRAN 77, which predates the IEEE standard (set in 1984), but I don't know enough FORTRAN 77 to say whether or not there's some issue with the interface here.

If you're looking to just round up regardless, the np.ceil function (ceiling function in general), will do this. If you're looking for the opposite (always rounding down), the np.floor function will achieve this.

2 of 9
18

This is in fact exactly the rounding specified by the IEEE floating point standard IEEE 754 (1985 and 2008). It is intended to make rounding unbiased. In normal probability theory, a random number between two integers has zero probability of being exactly N + 0.5, so it shouldn't matter how you round it because that case never happens. But in real programs, numbers are not random and N + 0.5 occurs quite often. (In fact, you have to round 0.5 every time a floating point number loses 1 bit of precision!) If you always round 0.5 up to the next largest number, then the average of a bunch rounded numbers is likely to be slightly larger than the average of the unrounded numbers: this bias or drift can have very bad effects on some numerical algorithms and make them inaccurate.

The reason rounding to even is better than rounding to odd is that the last digit is guaranteed to be zero, so if you have to divide by 2 and round again, you don't lose any information at all.

In summary, this kind of rounding is the best that mathematicians have been able to devise, and you should WANT it under most circumstances. Now all we need to do is get schools to start teaching it to children.

🌐
GeeksforGeeks
geeksforgeeks.org › numpy › numpy-round_-python
numpy.round_() in Python - GeeksforGeeks
December 6, 2024 - In this example, numpy.round_() rounds each element of the array to the nearest integer.
🌐
w3resource
w3resource.com › python-exercises › numpy › python-numpy-math-exercise-9.php
NumPy: Round elements of the array to the nearest integer - w3resource
August 29, 2025 - Original array: [-0.7 -1.5 -1.7 0.3 1.5 1.8 2. ] Round elements of the array to the nearest integer: [-1. -2. -2. 0. 2. 2. 2.] ... numpy.rint function is used to round elements of the array to the nearest integer.
🌐
GitHub
github.com › numpy › numpy › issues › 11810
round() returns floating point, not int, for some numpy floats when no second arg · Issue #11810 · numpy/numpy
August 23, 2018 - If ndigits is omitted or is None, it returns the nearest integer to its input. This works incorrectly in the case of np.float64, which returns a float. I believe the __round__ method is calling __rint__, which should return an integer but doesn't. import numpy as np In [50]: round(1.0) Out[50]: 1 In [51]: round(float(1.0)) Out[51]: 1 In [52]: round(np.float(1.0)) Out[52]: 1 In [53]: round(np.float64(1.0)) Out[53]: 1.0 ·
Author   tfawcett
🌐
Spark By {Examples}
sparkbyexamples.com › home › python › python numpy round() array function
Python NumPy round() Array Function - Spark By {Examples}
March 27, 2024 - Python NumPy round() is a built-in function used to return the rounded values of the source array to the nearest integer. It also takes the decimal values to be rounded. If the decimal places to be rounded are specified then it returns an array ...
🌐
GeeksforGeeks
geeksforgeeks.org › python › how-to-round-elements-of-the-numpy-array-to-the-nearest-integer
How to round elements of the NumPy array to the nearest integer? - GeeksforGeeks
July 15, 2025 - import numpy as n # create array y = n.array([-0.2, 0.7, -1.4, -4.5, -7.6, -19.7]) print("Original array:", end=" ") print(y) # round to nearest integer y = n.rint(y) print("After rounding off:", end=" ") print(y)
🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.round.html
numpy.round — NumPy v2.1 Manual
Unless out was specified, a new array is created. A reference to the result is returned. The real and imaginary parts of complex numbers are rounded separately.
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.round.html
numpy.round — NumPy v2.4 Manual
Unless out was specified, a new array is created. A reference to the result is returned. The real and imaginary parts of complex numbers are rounded separately.
Top answer
1 of 2
10

np.around(x).astype(int) and x.astype(int) don't produce the same values. The former rounds even (it's the same as ((x*x>=0+0.5) + (x*x<0-0.5)).astype(int)) whereas the latter rounds towards zero. However,

y = np.trunc(x).astype(int)
z = x.astype(int)

shows y==z but calculating y is much slower. So it's the np.truncand np.around functions which are slow.

In [165]: x.dtype
Out[165]: dtype('float64')
In [168]: y.dtype
Out[168]: dtype('int64')

So np.trunc(x) rounds towards zero from double to double. Then astype(int) has to convert double to int64.

Internally I don't know what python or numpy are doing but I know how I would do this in C. Let's discuss some hardware. With SSE4.1 it's possible to do round, floor, ceil, and trunc from double to double using:

_mm_round_pd(a, 0); //round: round even
_mm_round_pd(a, 1); //floor: round towards minus infinity
_mm_round_pd(a, 2); //ceil:  round towards positive infinity
_mm_round_pd(a, 3); //trunc: round towards zero

but numpy needs to support systems without SSE4.1 as well so it would have to build without SSE4.1 as well as with SSE4.1 and then use a dispatcher.

But to do this from double directly to int64 using SSE/AVX is not efficient until AVX512. However, it is possible to round double to int32 efficiently using only SSE2:

_mm_cvtpd_epi32(a);  //round double to int32 then expand to int64
_mm_cvttpd_epi32(a); //trunc double to int32 then expand to int64

These converts two doubles to two int64.

In your case this would work fine since the range is certainly within int32. But unless python knows the range fits in int32 it can't assume this so it would have to round or trunc to int64 which is slow. Also, once again numpy would have to build to support SSE2 to do this anyway.

But maybe you could have used a single floating point array to begin with. In that case you could have done:

_mm_cvtps_epi32(a); //round single to int32
_mm_cvttps_epi32(a) //trunc single to int32

These convert four singles to four int32.

So to answer your question SSE2 can round or truncated from double to int32 efficiently. AVX512 will be able to round or truncated from double to int64 efficiently as well using _mm512_cvtpd_epi64(a) or _mm512_cvttpd_epi64(a). SSE4.1 can round/trunc/floor/ceil from float to float or double to double efficiently.

2 of 2
1

As pointed out by @jme in the comments, the rint and around functions must work out whether to round the fractions up or down to the nearest integer. In contrast, the astype function will always round down so it can immediately discard the decimal information. There are a number of other functions that do the same thing. Also, you could improve the speed by using a lower number of bits for the integer. However you must be careful that you can accommodate the full range of your input data.

%%timeit
np.int8(x)
10000 loops, best of 3: 165 µs per loop

Note, this does not store values outside the range -128 to 127 as it's 8-bit. Some values in your example fall outside this range.

Of all the others I tried, np.intc seems to be the fastest:

%%timeit
np.int16(x)
10000 loops, best of 3: 186 µs per loop

%%timeit
np.intc(x)
10000 loops, best of 3: 169 µs per loop

%%timeit
np.int0(x)
10000 loops, best of 3: 170 µs per loop

%%timeit
np.int_(x)
10000 loops, best of 3: 188 µs per loop

%%timeit
np.int32(x)
10000 loops, best of 3: 187 µs per loop

%%timeit
    np.trunc(x)
1000 loops, best of 3: 940 µs per loop

Your examples, on my machine:

%%timeit
np.around(x)
1000 loops, best of 3: 1.48 ms per loop

%%timeit
np.rint(x)
1000 loops, best of 3: 1.49 ms per loop

%%timeit
x.astype(int)
10000 loops, best of 3: 188 µs per loop