Fast method to round half up or down in python

Viewed 954

I have a numpy array of complex numbers and need to create a new array with rounded real and imaginary parts where the rounding at half is either toward zero or toward infinity.

There are several recommendations on stackoverflow for using the decimal package which allows one to specify different types of rounding. For an array of complex numbers x the following code worked, but was very slow:

    rounded_array = np.array([
        float(Decimal(x.real).quantize(0, rounding=ROUND_HALF_DOWN)) + 1j * \
        float(Decimal(x.imag).quantize(0, rounding=ROUND_HALF_DOWNs)) for x in arr])

What are some simple but faster alternatives to this? This solution was suggested: How to always round up a XX.5 in numpy However, it applies only to real arrays and is much slower than the solutions suggested below.

2 Answers

Fast in-place rounding halves down:

arr = arr.view(float)
m = arr % 1. <= .5
arr[m] = np.floor(arr[m])
arr[~m] = np.ceil(arr[~m])
arr = arr.view(complex)

(use m = arr % 1. < .5 to round halves up)

If you need a new array instead of changing the existing array in-place, change the first line to arr = arr.view(float).copy('K').

For a 1000 elements array this is about 100 times faster than the original solution.


UPDATE for negative numbers as per comment below:

m = arr % 1. == .5
arr[m] = np.trunc(arr[m])
arr[~m] = np.round(arr[~m])

Timings for

x = np.arange(-1000, 1000, .1)
arr = x + 1j * x

%%timeit
rounded_array = np.array([
        float(Decimal(x.real).quantize(0, rounding=ROUND_HALF_DOWN)) + 1j * \
        float(Decimal(x.imag).quantize(0, rounding=ROUND_HALF_DOWN)) for x in arr])
        
1.83 s ± 27.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
arr1 = arr.view(float).copy('K')
m = arr1 % 1. == .5
arr1[m] = np.trunc(arr1[m])
arr1[~m] = np.round(arr1[~m])
arr1 = arr1.view(complex)


1.78 ms ± 18.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

You can round the real and imaginary part independently and then create a new array from it:

rounded_array = np.fix(arr.real) + 1j*np.fix(arr.imag)
Related