8000 BUG: Make floating remainder ufunc more exact. by charris · Pull Request #7237 · numpy/numpy · GitHub
[go: up one dir, main page]

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
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
TST: Test remainder corner cases and exact for small ints.
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.
  • Loading branch information
charris committed Feb 13, 2016
commit e99bf35680a9ab492f9fe107a23a5e2bce1c11dd
36 changes: 0 additions & 36 deletions numpy/core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -2448,42 +2448,6 @@ def test_conjugate(self):
assert_raises(AttributeError, lambda: a.conj())
assert_raises(AttributeError, lambda: a.conjugate())

def test_divmod_basic(self):
dt = np.typecodes['AllInteger'] + np.typecodes['Float']
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']:
continue
if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']:
continue
fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s'
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*71, dtype=dt1)
b = np.array(sg2*19, dtype=dt2)
div, rem = divmod(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)

def test_divmod_roundoff(self):
# gh-6127
dt = 'fdg'
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s'
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*78*6e-8, dtype=dt1)
b = np.array(sg2*6e-8, dtype=dt2)
div, rem = divmod(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)


class TestBinop(object):
def test_inplace(self):
# test refcount 1 inplace conversion
Expand Down
75 changes: 69 additions & 6 deletions numpy/core/tests/test_scalarmath.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import sys
import itertools
import warnings
import operator

import numpy as np
from numpy.testing.utils import _gen_alignment_data
Expand Down Expand Up @@ -137,8 +139,12 @@ def test_mixed_types(self):
assert_almost_equal(result, 9, err_msg=msg)


class TestDivmod(TestCase):
def test_divmod_basic(self):
class TestModulus(TestCase):

floordiv = operator.floordiv
mod = operator.mod

def test_modulus_basic(self):
dt = np.typecodes['AllInteger'] + np.typecodes['Float']
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
Expand All @@ -150,29 +156,86 @@ def test_divmod_basic(self):
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*71, dtype=dt1)[()]
b = np.array(sg2*19, dtype=dt2)[()]
div, rem = divmod(a, b)
div = self.floordiv(a, b)
rem = self.mod(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)

def test_divmod_roundoff(self):
def test_float_modulus_exact(self):
# test that float results are exact for small integers. This also
# holds for the same integers scaled by powers of two.
nlst = list(range(-127, 0))
plst = list(range(1, 128))
dividend = nlst + [0] + plst
divisor = nlst + plst
arg = list(itertools.product(dividend, divisor))
tgt = list(divmod(*t) for t in arg)

a, b = np.array(arg, dtype=int).T
# convert exact integer results from Python to float so that
# signed zero can be used, it is checked.
tgtdiv, tgtrem = np.array(tgt, dtype=float).T
tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv)
tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem)

for dt in np.typecodes['Float']:
msg = 'dtype: %s' % (dt,)
fa = a.astype(dt)
fb = b.astype(dt)
# use list comprehension so a_ and b_ are scalars
div = [self.floordiv(a_, b_) for a_, b_ in zip(fa, fb)]
rem = [self.mod(a_, b_) for a_, b_ in zip(fa, fb)]
assert_equal(div, tgtdiv, err_msg=msg)
assert_equal(rem, tgtrem, err_msg=msg)

def test_float_modulus_roundoff(self):
# gh-6127
dt = 'fdg'
dt = np.typecodes['Float']
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s'
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*78*6e-8, dtype=dt1)[()]
b = np.array(sg2*6e-8, dtype=dt2)[()]
div, rem = divmod(a, b)
div = self.floordiv(a, b)
rem = self.mod(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)

def test_float_modulus_corner_cases(self):
# Check remainder magnitude.
for dt in np.typecodes['Float']:
b = np.array(1.0, dtype=dt)
a = np.nextafter(np.array(0.0, dtype=dt), -b)
rem = self.mod(a, b)
assert_(rem < b, 'dt: %s' % dt)
rem = self.mod(-a, -b)
assert_(rem > -b, 'dt: %s' % dt)

# Check nans, inf
with warnings.catch_warnings():
warnings.simplefilter('always')
warnings.simplefilter('ignore', RuntimeWarning)
for dt in np.typecodes['Float']:
fone = np.array(1.0, dtype=dt)
fzer = np.array(0.0, dtype=dt)
finf = np.array(np.inf, dtype=dt)
fnan = np.array(np.nan, dtype=dt)
rem = self.mod(fone, fzer)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = self.mod(fone, finf)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = self.mod(fone, fnan)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = self.mod(finf, fone)
assert_(np.isnan(rem), 'dt: %s' % dt)


class TestComplexDivision(TestCase):
def test_zero_division(self):
Expand Down
95 changes: 95 additions & 0 deletions numpy/core/tests/test_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import platform
import warnings
import itertools

from numpy.testing.utils import _gen_alignment_data
import numpy.core.umath as ncu
Expand Down Expand Up @@ -222,6 +223,100 @@ def test_floor_division_complex(self):
assert_equal(y, [1.e+110, 0], err_msg=msg)


class TestRemainder(TestCase):

def test_remainder_basic(self):
dt = np.typecodes['AllInteger'] + np.typecodes['Float']
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
if sg1 == -1 and dt1 in np.typecodes['UnsignedInteger']:
continue
if sg2 == -1 and dt2 in np.typecodes['UnsignedInteger']:
continue
fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s'
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*71, dtype=dt1)
b = np.array(sg2*19, dtype=dt2)
div = np.floor(a/b)
rem = np.remainder(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)

def test_float_remainder_exact(self):
# test that float results are exact for small integers. This also
# holds for the same integers scaled by powers of two.
nlst = list(range(-127, 0))
plst = list(range(1, 128))
dividend = nlst + [0] + plst
divisor = nlst + plst
arg = list(itertools.product(dividend, divisor))
tgt = list(divmod(*t) for t in arg)

a, b = np.array(arg, dtype=int).T
# convert exact integer results from Python to float so that
# signed zero can be used, it is checked.
tgtdiv, tgtrem = np.array(tgt, dtype=float).T
tgtdiv = np.where((tgtdiv == 0.0) & ((b < 0) ^ (a < 0)), -0.0, tgtdiv)
tgtrem = np.where((tgtrem == 0.0) & (b < 0), -0.0, tgtrem)

for dt in np.typecodes['Float']:
msg = 'dtype: %s' % (dt,)
fa = a.astype(dt)
fb = b.astype(dt)
div = np.floor(fa/fb)
rem = np.remainder(fa, fb)
assert_equal(div, tgtdiv, err_msg=msg)
assert_equal(rem, tgtrem, err_msg=msg)

def test_float_remainder_roundoff(self):
# gh-6127
dt = np.typecodes['Float']
for dt1, dt2 in itertools.product(dt, dt):
for sg1, sg2 in itertools.product((+1, -1), (+1, -1)):
fmt = 'dt1: %s, dt2: %s, sg1: %s, sg2: %s'
msg = fmt % (dt1, dt2, sg1, sg2)
a = np.array(sg1*78*6e-8, dtype=dt1)
b = np.array(sg2*6e-8, dtype=dt2)
div = np.floor(a/b)
rem = np.remainder(a, b)
assert_allclose(div*b + rem, a, err_msg=msg)
if sg2 == -1:
assert_(b < rem <= 0, msg)
else:
assert_(b > rem >= 0, msg)

def test_float_remainder_corner_cases(self):
# Check remainder magnitude.
for dt in np.typecodes['Float']:
b = np.array(1.0, dtype=dt)
a = np.nextafter(np.array(0.0, dtype=dt), -b)
rem = np.remainder(a, b)
assert_(rem < b, 'dt: %s' % dt)
rem = np.remainder(-a, -b)
assert_(rem > -b, 'dt: %s' % dt)

# Check nans, inf
with warnings.catch_warnings():
warnings.simplefilter('always')
warnings.simplefilter('ignore', RuntimeWarning)
for dt in np.typecodes['Float']:
fone = np.array(1.0, dtype=dt)
fzer = np.array(0.0, dtype=dt)
finf = np.array(np.inf, dtype=dt)
fnan = np.array(np.nan, dtype=dt)
rem = np.remainder(fone, fzer)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = np.remainder(fone, finf)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = np.remainder(fone, fnan)
assert_(np.isnan(rem), 'dt: %s' % dt)
rem = np.remainder(finf, fone)
assert_(np.isnan(rem), 'dt: %s' % dt)


class TestCbrt(TestCase):
def test_cbrt_scalar(self):
assert_almost_equal((np.cbrt(np.float32(-2.5)**3)), -2.5)
Expand Down
0