-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
Inexact result on floating-point remainder #7224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
A comparison of the various approaches:
|
I don't think 1 ulp counts as in issue in floating point. The new approach works better in general than the old. |
I was a bit surprised here because the computation can be done exactly. But, well, agreed that it's not a major issue. |
I think the problem is somewhat philosophical. You know that you meant 5 and 2.5 exactly, but the computer only knows that 5 and 2.5 are approximations of the true numbers, which may not be exactly represented. OTOH, I won't argue that further improvements can't be made. The original problem was to keep the results of floor division and remainder compatible, and when fmod (%) was used for the latter it was possible to have large inconsistencies. The current implementation guarantees (I think) that the remainder is always less in absolute value than the divisor and has the correct sign. |
As @pitrou has mentioned, those particular numbers are exactly represented; a rational number with a small enough numerator and with a denominator that is a small enough power of 2 is represented exactly in floating point. |
@argriffing You missed the point. All floating point numbers exactly represent themselves. |
For fun, also
|
I agree that an insignificant error in np.remainder is a small price for having fixed the coordination issues among functions like divmod/div/truediv/floordiv/remainder/etc. |
While a small error is definitely preferable over the wrong answers given before, I can see it being disconcerting that
I'm slightly puzzled by the inconsistency with
But in the end, it may just be a question of "pick your poison":
|
As I was getting curious about what python itself does, I went ahead and looked for it -- not that easy to trace, but here it is: http://sources.debian.net/src/python2.7/2.7.11-3/Objects/floatobject.c/?hl=2083#L749 In the end, I think the premise is slightly different: python takes But it may be possible to remove the rounding problems, at the cost of an extra multiplication. Right now, we have (https://github.com/numpy/numpy/blob/master/numpy/core/src/umath/loops.c.src#L1710):
I think if we were to change this to
it would remove the "regression". At least
(I checked on the command line, but not with actual change to C code, that this did not immediately bring back the original problem.) |
Interesting, that may be better. What I liked about the original is that the abs value of the result is guaranteed to be less than |
Hmm,
|
@mhvk I think that works. |
Turns out that they have different definitions in terms of how they are computed. |
Well, turns out my computation doesn't work for negative numbers close to zero. The problem is that ieee floating point precision is symmetric around zero, but floor is not. Basically, the python |
@charris: if we're having doubts at the 11th hour, should we pull it back to master and wait for 1.12? |
Don't forget that in 1.10 one gets answers that are wrong, not just slightly off! |
@njsmith It's an interesting problem ;) The tiny negative number bit is inherent, so present in the python version also. For exactly represented numbers using few bits, the ideal computation is |
The intent here is to make sure the following is true for floating numbers x (dividend) and y (divisor) and r = remainder(x, y). * If both x and y are small integer floats, r is exact. * The sign of r is the same as the sign of y, including signed zero. * The magnitude of r is strictly less than the magnitude of y. * y ~= r + y*floor(x/y), i.e., r is compatible with floor. Remainder functions are also added to npy_math for all supported floats 'efdg'. This helps keep scalar and array results in sync. Explicit loops are also made for remainder as the speedup over using generic loops is about 20%. Note that the NumPy version of remainder differs from that in Python, as the latter is based around the fmod function rather than floor. Closes numpy#7224.
The following numpy ufuncs are reimplemented using the npy_divmod function. - remainder ('%') - floor_divide ('//') Example of differences Currently In [1]: a = np.array(78 * 6e-8) In [2]: b = np.array(6e-8) In [3]: a // b Out[3]: 77.0 In [4]: a % b Out[4]: 5.9999999999999651e-08 Previously In [1]: a = np.array(78 * 6e-8) In [2]: b = np.array(6e-8) In [3]: a // b Out[3]: 78.0 In [4]: a % b Out[4]: 0.0 The first agrees with the Python values for the same operation and is a bit more accurate for the input values as represented internally. Closes numpy#7224.
The following numpy ufuncs are reimplemented using the npy_divmod function. - remainder ('%') - floor_divide ('//') Example of differences Currently In [1]: a = np.array(78 * 6e-8) In [2]: b = np.array(6e-8) In [3]: a // b Out[3]: 77.0 In [4]: a % b Out[4]: 5.9999999999999651e-08 Previously In [1]: a = np.array(78 * 6e-8) In [2]: b = np.array(6e-8) In [3]: a // b Out[3]: 78.0 In [4]: a % b Out[4]: 0.0 The first agrees with the Python values for the same operation and is a bit more accurate for the input values as represented internally. Closes numpy#7224.
This seems to be a regression in 11.0 or in master (note the values are exactly representable):
Compare to:
The text was updated successfully, but these errors were encountered: