3

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.

rhz
  • 960
  • 14
  • 29

2 Answers2

2

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)
Stef
  • 28,728
  • 2
  • 24
  • 52
  • This solution does not appear to round to away from zero at halves for both positive and negative numbers. I desired rnd(-1.5) = -2 and rnd(1.5) = 2 – rhz Jun 30 '20 at 18:58
  • @rhz please see my updated answer – Stef Jun 30 '20 at 21:03
  • This is indeed much faster. How would it need to be modified for rounding away from zero? – rhz Jul 01 '20 at 22:07
  • `arr[m] = np.trunc(arr[m]) + 1` (`arr[~m]` left unchanged) – Stef Jul 02 '20 at 06:36
0

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)
a_guest
  • 34,165
  • 12
  • 64
  • 118