summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeveloper-Ecosystem-Engineering <65677710+Developer-Ecosystem-Engineering@users.noreply.github.com>2022-02-01 10:59:29 -0800
committerGitHub <noreply@github.com>2022-02-01 12:59:29 -0600
commit49c560c22f137907ea6a240591e49b004f28444b (patch)
treece9096f4242d6a42a15baabfc51b5ca010e8a2df
parentb6e6b838f1dd5f689ef8bac7a40ee814be7cd362 (diff)
downloadnumpy-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.src14
-rw-r--r--numpy/core/tests/test_umath.py85
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):