diff options
author | Developer-Ecosystem-Engineering <65677710+Developer-Ecosystem-Engineering@users.noreply.github.com> | 2022-02-01 10:59:29 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-01 12:59:29 -0600 |
commit | 49c560c22f137907ea6a240591e49b004f28444b (patch) | |
tree | ce9096f4242d6a42a15baabfc51b5ca010e8a2df | |
parent | b6e6b838f1dd5f689ef8bac7a40ee814be7cd362 (diff) | |
download | numpy-49c560c22f137907ea6a240591e49b004f28444b.tar.gz |
BUG: Loss of precision in longdouble min (#20872)
* BUG: Loss of precision in longdouble min
Generic reuse in the latest changes around `min` works unless the macro is redefined for SIMD.
This change avoids `scalar_min_f` for generic comparisons (as it can be redefined) and defines is separately as `scalar_min`
* Add tests as requested
Adding tests like suggested in https://github.com/numpy/numpy/issues/20863
* MAINT: Use `np.longdouble` rather than float128 for min/max tests
Co-authored-by: Sebastian Berg <sebastian@sipsolutions.net>
-rw-r--r-- | numpy/core/src/umath/loops_minmax.dispatch.c.src | 14 | ||||
-rw-r--r-- | numpy/core/tests/test_umath.py | 85 |
2 files changed, 93 insertions, 6 deletions
diff --git a/numpy/core/src/umath/loops_minmax.dispatch.c.src b/numpy/core/src/umath/loops_minmax.dispatch.c.src index 708270016..ba2288f0b 100644 --- a/numpy/core/src/umath/loops_minmax.dispatch.c.src +++ b/numpy/core/src/umath/loops_minmax.dispatch.c.src @@ -22,12 +22,14 @@ #define scalar_max_i(A, B) ((A > B) ? A : B) #define scalar_min_i(A, B) ((A < B) ? A : B) // fp, propagates NaNs -#define scalar_max_f(A, B) ((A >= B || npy_isnan(A)) ? A : B) -#define scalar_max_d scalar_max_f -#define scalar_max_l scalar_max_f -#define scalar_min_f(A, B) ((A <= B || npy_isnan(A)) ? A : B) -#define scalar_min_d scalar_min_f -#define scalar_min_l scalar_min_f +#define scalar_max(A, B) ((A >= B || npy_isnan(A)) ? A : B) +#define scalar_max_f scalar_max +#define scalar_max_d scalar_max +#define scalar_max_l scalar_max +#define scalar_min(A, B) ((A <= B || npy_isnan(A)) ? A : B) +#define scalar_min_f scalar_min +#define scalar_min_d scalar_min +#define scalar_min_l scalar_min // fp, ignores NaNs #define scalar_maxp_f fmaxf #define scalar_maxp_d fmax diff --git a/numpy/core/tests/test_umath.py b/numpy/core/tests/test_umath.py index e7fee46b7..575807d89 100644 --- a/numpy/core/tests/test_umath.py +++ b/numpy/core/tests/test_umath.py @@ -1728,6 +1728,27 @@ class TestMaximum(_FilterInvalids): assert_equal(np.maximum(arr1[:6:2], arr2[::3], out=out[::3]), np.array([-2.0, 10., np.nan])) assert_equal(out, out_maxtrue) + def test_precision(self): + dtypes = [np.float16, np.float32, np.float64, np.longdouble] + + for dt in dtypes: + dtmin = np.finfo(dt).min + dtmax = np.finfo(dt).max + d1 = dt(0.1) + d1_next = np.nextafter(d1, np.inf) + + test_cases = [ + # v1 v2 expected + (dtmin, -np.inf, dtmin), + (dtmax, -np.inf, dtmax), + (d1, d1_next, d1_next), + (dtmax, np.nan, np.nan), + ] + + for v1, v2, expected in test_cases: + assert_equal(np.maximum([v1], [v2]), [expected]) + assert_equal(np.maximum.reduce([v1, v2]), expected) + class TestMinimum(_FilterInvalids): def test_reduce(self): @@ -1799,6 +1820,28 @@ class TestMinimum(_FilterInvalids): assert_equal(np.minimum(arr1[:6:2], arr2[::3], out=out[::3]), np.array([-4.0, 1.0, np.nan])) assert_equal(out, out_mintrue) + def test_precision(self): + dtypes = [np.float16, np.float32, np.float64, np.longdouble] + + for dt in dtypes: + dtmin = np.finfo(dt).min + dtmax = np.finfo(dt).max + d1 = dt(0.1) + d1_next = np.nextafter(d1, np.inf) + + test_cases = [ + # v1 v2 expected + (dtmin, np.inf, dtmin), + (dtmax, np.inf, dtmax), + (d1, d1_next, d1), + (dtmin, np.nan, np.nan), + ] + + for v1, v2, expected in test_cases: + assert_equal(np.minimum([v1], [v2]), [expected]) + assert_equal(np.minimum.reduce([v1, v2]), expected) + + class TestFmax(_FilterInvalids): def test_reduce(self): dflt = np.typecodes['AllFloat'] @@ -1840,6 +1883,27 @@ class TestFmax(_FilterInvalids): out = np.array([0, 0, nan], dtype=complex) assert_equal(np.fmax(arg1, arg2), out) + def test_precision(self): + dtypes = [np.float16, np.float32, np.float64, np.longdouble] + + for dt in dtypes: + dtmin = np.finfo(dt).min + dtmax = np.finfo(dt).max + d1 = dt(0.1) + d1_next = np.nextafter(d1, np.inf) + + test_cases = [ + # v1 v2 expected + (dtmin, -np.inf, dtmin), + (dtmax, -np.inf, dtmax), + (d1, d1_next, d1_next), + (dtmax, np.nan, dtmax), + ] + + for v1, v2, expected in test_cases: + assert_equal(np.fmax([v1], [v2]), [expected]) + assert_equal(np.fmax.reduce([v1, v2]), expected) + class TestFmin(_FilterInvalids): def test_reduce(self): @@ -1882,6 +1946,27 @@ class TestFmin(_FilterInvalids): out = np.array([0, 0, nan], dtype=complex) assert_equal(np.fmin(arg1, arg2), out) + def test_precision(self): + dtypes = [np.float16, np.float32, np.float64, np.longdouble] + + for dt in dtypes: + dtmin = np.finfo(dt).min + dtmax = np.finfo(dt).max + d1 = dt(0.1) + d1_next = np.nextafter(d1, np.inf) + + test_cases = [ + # v1 v2 expected + (dtmin, np.inf, dtmin), + (dtmax, np.inf, dtmax), + (d1, d1_next, d1), + (dtmin, np.nan, dtmin), + ] + + for v1, v2, expected in test_cases: + assert_equal(np.fmin([v1], [v2]), [expected]) + assert_equal(np.fmin.reduce([v1, v2]), expected) + class TestBool: def test_exceptions(self): |