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

Ahmed Fasih
  • 6,458
  • 7
  • 54
  • 95
  • 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? – Faultier Sep 24 '13 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? – Ahmed Fasih Sep 24 '13 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: http://docs.scipy.org/doc/numpy/reference/generated/numpy.around.html?highlight=round#numpy.around – Faultier Sep 24 '13 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.](http://en.wikipedia.org/wiki/Rounding#Tie-breaking) – Schorsch Sep 24 '13 at 17:55

2 Answers2

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
jabaldonedo
  • 25,822
  • 8
  • 77
  • 77
  • 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: http://blog.frama-c.com/index.php?post/2013/05/02/nearbyintf1 – Ahmed Fasih Sep 24 '13 at 14:54
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
Community
  • 1
  • 1
Veedrac
  • 58,273
  • 15
  • 112
  • 169
  • "This is not platform dependent, but a purposeful implementation detail." Does this mean that Python's and Matlab's round is platform-dependent? – Ahmed Fasih Sep 24 '13 at 14:45
  • 2
    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 Sep 24 '13 at 14:55
  • 2
    @Veedrac: Actually, Python's built-in `round` rounds *away* from zero. – John Y Dec 04 '13 at 23:55
  • What is the scope of this workaround? The whole executable? The current module? – detly Dec 24 '13 at 07:47
  • Also, how portable is this? – detly Dec 24 '13 at 07:52
  • @detly I suggest testing and asking at the linked answer; this is merely a solution I've found, not one I've used. – Veedrac Dec 24 '13 at 09:36