5

Simplified question

Can I make Numpy agree with Matlab and Python's round?

Matlab 2013a:

>> round(-0.5)
ans =
    -1

Python (using a Numpy array, or just a scalar, same result):

>>> import numpy
>>> round(numpy.array(-0.5))
-1.0

Numpy, the odd one out:

>>> import numpy
>>> numpy.round(numpy.array(-0.5))
-0

Is this difference in round platform dependent?

Original question

Matlab comes with a file "handel.mat" containing some audio data:

>> which handel.mat
C:\Program Files\MATLAB\R2013a\toolbox\matlab\audiovideo\handel.mat
>> load handel
>> soundsc(y) % play the short audio clip

I want to work with this data in Python so I use scipy.io.loadmat [1]. Specifically, I want to scale the audio's values to span the entire range of 16-bit signed integer, i.e., the smallest value of the audio signal gets mapped to -2^15 and the largest one to 2^15-1. I was surprised when doing this in Matlab gave me different results than Python:

Matlab:

>> load handel
>> int16(round(interp1([min(y), max(y)], [-2^15, 2^15-1], y(1:10))))
ans =
     -1              %%% <-- Different from Python
   -253
  -3074
  -1277
    252
   1560
    772
  -1025
  -1277
  -3074

Python:

In [1]: import numpy as np

In [2]: import scipy.io as io

In [3]: mat = io.loadmat('handel.mat')

In [4]: np.int16(np.round(np.interp(mat['y'][:10], [mat['y'].min(), mat['y'].max()], [-2.0**15, 2.0**15-1.0])))
Out[4]:
array([[    0],      ### <-- Different from Matlab
       [ -253],
       [-3074],
       [-1277],
       [  252],
       [ 1560],
       [  772],
       [-1025],
       [-1277],
       [-3074]], dtype=int16)

There are actually 1231 samples (out of 73113 total) where the Python and Matlab differ. I think I'm being careful with my types, but really, there's very little error surface for type bugs to creep in here: loadmat should infer the types from the MAT file, and int16 can't differ that much between the two systems.

Added The first element of the output of the interp/interp1d commands are both -0.5 (printing it to the 100th decimal place in both Python and Matlab confirms this), but rounding in Numpy (np.round) yields 0, while Matlab rounds it to -1. Is this a matter of Matlab rounding semantics? Furthermore Python's built-in non-Numpy round for -0.5 gives me -1! Whence this difference between Numpy's and Python's round functions? And will Python's round always match Matlab's?

Windows64, Matlab 8.1 (2013a), Python 2.7.4.

[1] http://docs.scipy.org/doc/scipy/reference/generated/scipy.io.loadmat.html

4
  • if you do not scale the values to int16, are the readouts the same in both cases? If yes, I would go further and check the behavior on a np.linspace() of the latter functions; round, interp and int16. I guess it could be a slightly different behavior of the round or interpolation function? Commented Sep 24, 2013 at 13:24
  • @Faultier check my edits at the bottom, it looks like the difference is in the output of round (Matlab vs Numpy vs Python). Suggestions? Commented Sep 24, 2013 at 13:27
  • I think this is subject of definition - what is the behavior that you want? I would just go with the according round function. Also, see the numpy manual draft on this topic: docs.scipy.org/doc/numpy/reference/generated/… Commented Sep 24, 2013 at 13:53
  • 1
    My $0.02: There are, maybe surprisingly at first, multiple definitions of how to round values which are exactly half way between two integers. The Wikipedia site on rounding has a good explanation on what is called tie-breaking. Commented Sep 24, 2013 at 17:55

2 Answers 2

2

I think you can take advantage of numpy.vectorize to create a custom numpy round function using standard python round function:

>>> import numpy as np
>>> myround = np.vectorize(lambda x: round(x))
>>> a = np.array([-0.5, 0.5, -1.5, 1.5, -2.5, 2.5, 3.5, -3.5])
>>> print myround(a)
 [-1.  1. -2.  2. -3.  3.  4. -4.]

Which is the same result that shows Matlab:

>> a = [-1.  1. -2.  2. -3.  3.  4. -4.];
>> round(a)
 ans =
    -1     1    -2     2    -3     3     4    -4
Sign up to request clarification or add additional context in comments.

1 Comment

Right, that's certainly an option (in fact, you can omit the lambda, i.e., myround = np.vectorize(round)). It sounds like rounding is more complicated than I thought: blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1
1

numpy.round, also known as numpy.around, rounds to the nearest even value for half-integers. This is not platform dependent, but a purposeful implementation detail.

If you wish to have a quick round without using Python, see this answer.

The summary is that there's a platform dependant hack to set rounding using fesetround via ctypes. From the post:

import numpy as np
import ctypes
FE_TONEAREST = 0x0000
FE_DOWNWARD = 0x0400
FE_UPWARD = 0x0800
FE_TOWARDZERO = 0x0c00
libc = ctypes.CDLL('libc.dylib')

v = 1. / (1<<23)
print repr(np.float32(1+v) - np.float32(v/2)) # prints 1.0
libc.fesetround(FE_UPWARD)
print repr(np.float32(1+v) - np.float32(v/2)) # prints 1.0000002

6 Comments

"This is not platform dependent, but a purposeful implementation detail." Does this mean that Python's and Matlab's round is platform-dependent?
No, they just use a different method. Numpy uses round-half-to-even, Python uses round-half-to-zero. The first is more useful for statistics, the second is more general-purpose.
@Veedrac: Actually, Python's built-in round rounds away from zero.
What is the scope of this workaround? The whole executable? The current module?
Also, how portable is this?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.