diff options
| author | Matti Picus <matti.picus@gmail.com> | 2022-03-22 03:39:45 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-22 03:39:45 -0600 |
| commit | 982fcd381b71dc6668ad7841cd51dd6061d10c07 (patch) | |
| tree | ca3267ad146211e37163217619f5a189e82b39a3 /numpy | |
| parent | d29f8b8b14bae94092dd29e8130e9545ff862f86 (diff) | |
| parent | dfaebc1e2e8ead4096bc131ef93008367ed4a93c (diff) | |
| download | numpy-982fcd381b71dc6668ad7841cd51dd6061d10c07.tar.gz | |
Merge pull request #21218 from seberg/issue21211
BUG: Use -0. as initial value for summation (internal only)
Diffstat (limited to 'numpy')
| -rw-r--r-- | numpy/core/src/umath/loops_utils.h.src | 10 | ||||
| -rw-r--r-- | numpy/core/tests/test_ufunc.py | 54 |
2 files changed, 61 insertions, 3 deletions
diff --git a/numpy/core/src/umath/loops_utils.h.src b/numpy/core/src/umath/loops_utils.h.src index 762e9ee59..df92bc315 100644 --- a/numpy/core/src/umath/loops_utils.h.src +++ b/numpy/core/src/umath/loops_utils.h.src @@ -79,7 +79,11 @@ static NPY_INLINE @type@ { if (n < 8) { npy_intp i; - @type@ res = 0.; + /* + * Start with -0 to preserve -0 values. The reason is that summing + * only -0 should return -0, but `0 + -0 == 0` while `-0 + -0 == -0`. + */ + @type@ res = -0.0; for (i = 0; i < n; i++) { res += @trf@(*((@dtype@*)(a + i * stride))); @@ -156,8 +160,8 @@ static NPY_INLINE void if (n < 8) { npy_intp i; - *rr = 0.; - *ri = 0.; + *rr = -0.0; + *ri = -0.0; for (i = 0; i < n; i += 2) { *rr += *((@ftype@ *)(a + i * stride + 0)); *ri += *((@ftype@ *)(a + i * stride + sizeof(@ftype@))); diff --git a/numpy/core/tests/test_ufunc.py b/numpy/core/tests/test_ufunc.py index f13c2667d..cb0f7bcbd 100644 --- a/numpy/core/tests/test_ufunc.py +++ b/numpy/core/tests/test_ufunc.py @@ -2507,3 +2507,57 @@ def test_ufunc_methods_floaterrors(method): with np.errstate(all="raise"): with pytest.raises(FloatingPointError): method(arr) + + +def _check_neg_zero(value): + if value != 0.0: + return False + if not np.signbit(value.real): + return False + if value.dtype.kind == "c": + return np.signbit(value.imag) + return True + +@pytest.mark.parametrize("dtype", np.typecodes["AllFloat"]) +def test_addition_negative_zero(dtype): + dtype = np.dtype(dtype) + if dtype.kind == "c": + neg_zero = dtype.type(complex(-0.0, -0.0)) + else: + neg_zero = dtype.type(-0.0) + + arr = np.array(neg_zero) + arr2 = np.array(neg_zero) + + assert _check_neg_zero(arr + arr2) + # In-place ops may end up on a different path (reduce path) see gh-21211 + arr += arr2 + assert _check_neg_zero(arr) + + +@pytest.mark.parametrize("dtype", np.typecodes["AllFloat"]) +@pytest.mark.parametrize("use_initial", [True, False]) +def test_addition_reduce_negative_zero(dtype, use_initial): + dtype = np.dtype(dtype) + if dtype.kind == "c": + neg_zero = dtype.type(complex(-0.0, -0.0)) + else: + neg_zero = dtype.type(-0.0) + + kwargs = {} + if use_initial: + kwargs["initial"] = neg_zero + else: + pytest.xfail("-0. propagation in sum currently requires initial") + + # Test various length, in case SIMD paths or chunking play a role. + # 150 extends beyond the pairwise blocksize; probably not important. + for i in range(0, 150): + arr = np.array([neg_zero] * i, dtype=dtype) + res = np.sum(arr, **kwargs) + if i > 0 or use_initial: + assert _check_neg_zero(res) + else: + # `sum([])` should probably be 0.0 and not -0.0 like `sum([-0.0])` + assert not np.signbit(res.real) + assert not np.signbit(res.imag) |
