Skip to content

BUG: Make floating remainder ufunc more exact. #7237

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

Closed
wants to merge 2 commits into from

Conversation

charris
Copy link
Member

@charris charris commented Feb 12, 2016

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 #7224.

@charris charris added this to the 1.11.0 release milestone Feb 12, 2016
@njsmith
Copy link
Member

njsmith commented Feb 12, 2016

You're getting failure on Travis due to c compiler warnings.

I'm nervous that this is a lot of lines of code and subtle semantics to be putting into a b4 release :-/ Would it make more sense to merge this to master and revert 34c2369 on 1.11?

@charris
Copy link
Member Author

charris commented Feb 12, 2016

No ;) It's not very subtle, actually.

@charris
Copy link
Member Author

charris commented Feb 12, 2016

Search and replace error.

@njsmith
Copy link
Member

njsmith commented Feb 12, 2016

I guess I'm just nervous because 34c2369 also seemed like a simple and obvious thing, but then it caused a bunch of downstream test failures and redesign during the beta period. So now we have a new simple and obvious change, and no beta period :-).

I know that the downstream test failures are partly just them being silly about floating point tolerances, but as a point of information, have you checked whether this patch fixes the failures in astropy?

@njsmith
Copy link
Member

njsmith commented Feb 12, 2016

(Also FYI you missed the # in the closes: ... tag, in case you find yourself editing that commit message for other reasons.)

@charris charris force-pushed the fix-remainder-again branch from 6db4107 to 7b018b2 Compare February 12, 2016 23:17
@charris
Copy link
Member Author

charris commented Feb 12, 2016

What bothers me is that the scalar tests didn't catch it, they should have.

@charris charris force-pushed the fix-remainder-again branch from 7b018b2 to 03ffddf Compare February 12, 2016 23:19
@charris
Copy link
Member Author

charris commented Feb 12, 2016

There is certainly going to be another beta, @mhvk owes us a masked array fix and we need to undo the integer indexing.

@charris
Copy link
Member Author

charris commented Feb 12, 2016

Certainly fixes the problem reported. Have you read the tests?

@charris
Copy link
Member Author

charris commented Feb 12, 2016

I didn't figure on merging this right away, I wanted to see the tests and I need to revisit after some rest.

@njsmith
Copy link
Member

njsmith commented Feb 12, 2016

I haven't (yet) read through the logic of these tests or the astropy tests, beyond checking that the astropy failures involved a comparison that only failed by a few ulps -- I figured you were doing that ;-)

@charris
Copy link
Member Author

charris commented Feb 12, 2016

Ha, the scalar slots for divmod and remainder were set to NULL at some point in the past. The scalar functions were not being used.

@charris
Copy link
Member Author

charris commented Feb 13, 2016

figured you were doing that

I added tests for exact results for small integers. Only -127..127, but that failed badly with the previous implementation. That also covers the same ints scaled by powers of two, so (6,5) covers the (3, 2.5) test.

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.
Test that remainder produces exact results for small ints that are
exactly represented by floats with small exact mantissas. This effective
tests for the same numbers scaled by powers of two.

Test corner cases. This tests cases involving zero division and infs
that should result in nans as well as cases involving negative numbers
very close to zero.

Previous divmod tests located in test_multarray are moved into
test_umath and renamed for remainder. They are a better there, as
remainder is a ufunc.
@charris
Copy link
Member Author

charris commented Feb 13, 2016

I'm tending towards a simple a - b*floor(a/b) in the float case, which has the virtue that it is easy to understand and agrees with, for instance, Fortran modulo. Maybe just insure that zero has the correct sign, but explain in the documentation that roundoff can lead to some odd appearing results in some extreme corner case. That form also works well for small integers scaled by powers of two. Small, of course, depends on the precision. Another option, not far off this is to allow abs(rem) <= abs(b).

@mhvk
Copy link
Contributor

mhvk commented Feb 14, 2016

@charris - sounds good to me!

@charris
Copy link
Member Author

charris commented Feb 15, 2016

I have been convinced that the best way to do this is to copy the Python implementation. Instead of trying to make the pair (floor, remainder) compatible, use (floor_divide,remainder) instead. That also has the advantage that both functions have two arguments, which allows us to do a better job of making them consistent. The floor_divide function is currently implemented using floor and if we make it compatible with Python there will be some changes in the results. However, it would probable be good if the // operator gave the same results for both Python float/float and NumPy float64/float64.

@mhvk
Copy link
Contributor

mhvk commented Feb 15, 2016

@charris - sounds good!

@charris
Copy link
Member Author

charris commented Feb 15, 2016

OK, closing this, new PR in progress.

@charris charris closed this Feb 15, 2016
@charris charris deleted the fix-remainder-again branch February 16, 2016 02:17
@charris charris removed this from the 1.11.0 release milestone Feb 19, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants