Skip to content

Commit 6613125

Browse files
committed
fixing -0.0 casting
1 parent 3f0cf90 commit 6613125

6 files changed

Lines changed: 57 additions & 46 deletions

File tree

quaddtype/numpy_quaddtype/src/casts.cpp

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -95,25 +95,23 @@ quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_
9595
memcpy(&roundtrip.sleef_value, &temp, sizeof(Sleef_quad));
9696
}
9797

98-
// Compare in SLEEF domain
99-
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value))
98+
// Compare in SLEEF domain && signbit preserved
99+
bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value));
100+
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
100101
return 1; // Both NaN
101-
if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value))
102+
if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
102103
return 1; // Equal
103-
if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO))
104-
return 1; // Both zeros
105104
}
106105
else {
107106
// Input was longdouble, output is SLEEF
108107
// Convert SLEEF back to longdouble for comparison
109-
roundtrip.longdouble_value = static_cast<long double>(Sleef_cast_to_doubleq1(out_val->sleef_value));
108+
roundtrip.longdouble_value = static_cast<long double>(cast_sleef_to_double(out_val->sleef_value));
110109

111-
// Compare in longdouble domain
112-
if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value))
110+
// Compare in longdouble domain && signbit preserved
111+
bool is_sign_preserved = (ld_signbit(&in_val->longdouble_value) == ld_signbit(&roundtrip.longdouble_value));
112+
if ((std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved)
113113
return 1;
114-
if (in_val->longdouble_value == roundtrip.longdouble_value)
115-
return 1;
116-
if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L)
114+
if ((in_val->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved)
117115
return 1;
118116
}
119117

@@ -158,7 +156,7 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[],
158156
quad_value out_val;
159157
if (backend_in == BACKEND_SLEEF)
160158
{
161-
out_val.longdouble_value = static_cast<long double>(Sleef_cast_to_doubleq1(in_val.sleef_value));
159+
out_val.longdouble_value = static_cast<long double>(cast_sleef_to_double(in_val.sleef_value));
162160
}
163161
else
164162
{
@@ -441,25 +439,20 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n
441439
}
442440
free(truncated_str);
443441

444-
// Compare original and roundtripped values
442+
// Compare original and roundtripped values along with signbit
445443
if (backend == BACKEND_SLEEF) {
446444
// NaN == NaN for same_value purposes
447-
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value))
448-
return 1;
449-
if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value))
445+
bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value));
446+
if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
450447
return 1;
451-
// Handle -0.0 == +0.0 case
452-
if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) &&
453-
Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO))
448+
if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
454449
return 1;
455450
}
456451
else {
457-
if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value))
458-
return 1;
459-
if (in_val->longdouble_value == roundtrip.longdouble_value)
452+
bool is_sign_preserved = (ld_signbit(&in_val->longdouble_value) == ld_signbit(&roundtrip.longdouble_value));
453+
if ((std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved)
460454
return 1;
461-
// Handle -0.0 == +0.0 case
462-
if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L)
455+
if ((in_val->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved)
463456
return 1;
464457
}
465458

@@ -1316,7 +1309,7 @@ inline npy_half
13161309
from_quad<spec_npy_half>(const quad_value *x, QuadBackendType backend)
13171310
{
13181311
if (backend == BACKEND_SLEEF) {
1319-
double d = Sleef_cast_to_doubleq1(x->sleef_value);
1312+
double d = cast_sleef_to_double(x->sleef_value);
13201313
return npy_double_to_half(d);
13211314
}
13221315
else {
@@ -1329,7 +1322,7 @@ inline float
13291322
from_quad<float>(const quad_value *x, QuadBackendType backend)
13301323
{
13311324
if (backend == BACKEND_SLEEF) {
1332-
return (float)Sleef_cast_to_doubleq1(x->sleef_value);
1325+
return (float)cast_sleef_to_double(x->sleef_value);
13331326
}
13341327
else {
13351328
return (float)x->longdouble_value;
@@ -1341,7 +1334,7 @@ inline double
13411334
from_quad<double>(const quad_value *x, QuadBackendType backend)
13421335
{
13431336
if (backend == BACKEND_SLEEF) {
1344-
return Sleef_cast_to_doubleq1(x->sleef_value);
1337+
return cast_sleef_to_double(x->sleef_value);
13451338
}
13461339
else {
13471340
return (double)x->longdouble_value;
@@ -1353,7 +1346,7 @@ inline long double
13531346
from_quad<long double>(const quad_value *x, QuadBackendType backend)
13541347
{
13551348
if (backend == BACKEND_SLEEF) {
1356-
return (long double)Sleef_cast_to_doubleq1(x->sleef_value);
1349+
return (long double)cast_sleef_to_double(x->sleef_value);
13571350
}
13581351
else {
13591352
return x->longdouble_value;
@@ -1367,23 +1360,20 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken
13671360
quad_value roundtrip = to_quad<T>(*y, backend);
13681361
if(backend == BACKEND_SLEEF)
13691362
{
1370-
if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value))
1363+
bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value));
1364+
if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
13711365
return 1;
1372-
if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value))
1373-
return 1;
1374-
// Handle -0.0 == +0.0 case: both zeros are considered equal for same_value casting
1375-
if(Sleef_icmpeqq1(x->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO))
1366+
if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved)
13761367
return 1;
13771368
}
13781369
else
13791370
{
1380-
if(std::isnan(x->longdouble_value) && std::isnan(roundtrip.longdouble_value))
1381-
return 1;
1382-
if(x->longdouble_value == roundtrip.longdouble_value)
1371+
bool is_sign_preserved = (ld_signbit(&x->longdouble_value) == ld_signbit(&roundtrip.longdouble_value));
1372+
if((std::isnan(x->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved)
13831373
return 1;
1384-
// Handle -0.0 == +0.0 case for longdouble backend
1385-
if(x->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L)
1374+
if((x->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved)
13861375
return 1;
1376+
13871377
}
13881378
Sleef_quad sleef_val = quad_to_sleef_quad(x, backend);
13891379
const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH);

quaddtype/numpy_quaddtype/src/constants.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212

1313
// Quad precision constants using sleef_q macro
1414
#define QUAD_PRECISION_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383)
15+
#define QUAD_PRECISION_NEG_ZERO sleef_q(-0x0000000000000LL, 0x0000000000000000ULL, -16383)
1516
#define QUAD_PRECISION_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0)
1617
#define QUAD_PRECISION_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384)
1718
#define QUAD_PRECISION_NINF sleef_q(-0x1000000000000LL, 0x0000000000000000ULL, 16384)

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <sleef.h>
22
#include <sleefquad.h>
33
#include <cmath>
4+
#include "constants.hpp"
45

56
// Quad Constants, generated with qutil
67
#define QUAD_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383)
@@ -1657,4 +1658,16 @@ static inline npy_bool
16571658
ld_logical_not(const long double *a)
16581659
{
16591660
return !ld_is_nonzero(a);
1660-
}
1661+
}
1662+
1663+
1664+
// Casting operations
1665+
static inline double
1666+
cast_sleef_to_double(const Sleef_quad in)
1667+
{
1668+
if (Sleef_icmpeqq1(in, QUAD_ZERO))
1669+
{
1670+
return quad_signbit(&in) ? -0.0 : 0.0;
1671+
}
1672+
return Sleef_cast_to_doubleq1(in);
1673+
}

quaddtype/numpy_quaddtype/src/scalar.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
104104
}
105105
double dval = PyFloat_AsDouble(py_float);
106106
Py_DECREF(py_float);
107-
108107
if (backend == BACKEND_SLEEF) {
109108
self->value.sleef_value = Sleef_cast_from_doubleq1(dval);
110109
}

quaddtype/numpy_quaddtype/src/scalar_ops.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ static PyObject *
213213
QuadPrecision_float(QuadPrecisionObject *self)
214214
{
215215
if (self->backend == BACKEND_SLEEF) {
216-
return PyFloat_FromDouble(Sleef_cast_to_doubleq1(self->value.sleef_value));
216+
return PyFloat_FromDouble(cast_sleef_to_double(self->value.sleef_value));
217217
}
218218
else {
219219
return PyFloat_FromDouble((double)self->value.longdouble_value);

quaddtype/tests/test_quaddtype.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5454,11 +5454,13 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing):
54545454
@pytest.mark.parametrize("dtype", [
54555455
np.float16, np.float32, np.float64, np.longdouble
54565456
])
5457-
@pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan')])
5457+
@pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan'), float("-nan")])
54585458
def test_same_value_cast_floats_special_values(self, dtype, val):
54595459
"""Test that special floating-point values roundtrip correctly."""
54605460
q = np.array([val], dtype=QuadPrecDType())
54615461
result = q.astype(dtype, casting="same_value")
5462+
if str(val).startswith("-"):
5463+
assert np.signbit(result), f"Sign bit failed for {dtype} with value {val}"
54625464
if np.isnan(val):
54635465
assert np.isnan(result), f"NaN failed for {dtype}"
54645466
else:
@@ -5539,7 +5541,7 @@ def test_same_value_cast_strings_enough_width(self, dtype):
55395541
values = [
55405542
"0.0", "-0.0", "1.0", "-1.0",
55415543
"3.14159265358979323846264338327950288", # pi with full quad precision
5542-
"inf", "-inf", "nan",
5544+
"inf", "-inf", "nan", "-nan",
55435545
"1.23e100", "-4.56e-100",
55445546
]
55455547

@@ -5548,6 +5550,8 @@ def test_same_value_cast_strings_enough_width(self, dtype):
55485550
result = q.astype(dtype, casting="same_value")
55495551
# Convert back and verify
55505552
back = result.astype(QuadPrecDType())
5553+
if str(val).startswith("-"):
5554+
assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}"
55515555
if np.isnan(q[0]):
55525556
assert np.isnan(back[0]), f"NaN roundtrip failed for {dtype}"
55535557
else:
@@ -5557,11 +5561,13 @@ def test_same_value_cast_strings_enough_width(self, dtype):
55575561
def test_same_value_cast_strings_narrow_width(self, dtype):
55585562
"""Test that string types with narrow width fail for values that need more precision."""
55595563
# Values that can fit in 10 chars should pass
5560-
passing_values = ["0.0", "1.0", "-1.0", "inf", "-inf", "nan"]
5564+
passing_values = ["0.0", "-0.0", "1.0", "-1.0", "inf", "-inf", "nan", "-nan"]
55615565
for val in passing_values:
55625566
q = np.array([val], dtype=QuadPrecDType())
55635567
result = q.astype(dtype, casting="same_value")
55645568
back = result.astype(QuadPrecDType())
5569+
if str(val).startswith("-"):
5570+
assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}"
55655571
if np.isnan(q[0]):
55665572
assert np.isnan(back[0])
55675573
else:
@@ -5591,7 +5597,7 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend)
55915597
0.0, -0.0, 1.0, -1.0,
55925598
0.5, 0.25, 0.125,
55935599
2.0, 4.0, 8.0,
5594-
"inf", "-inf", "nan",
5600+
"inf", "-inf", "nan", "-nan",
55955601
1e100, -1e-100,
55965602
str(2**52), # Largest consecutive integer in double
55975603
]
@@ -5601,7 +5607,9 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend)
56015607
result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value")
56025608

56035609
# Verify value is preserved
5604-
if val == "nan":
5610+
if str(val).startswith("-"):
5611+
assert np.signbit(result[0]), f"Sign bit failed for {val} in {src_backend} -> {dst_backend}"
5612+
if val in ["nan", "-nan"] :
56055613
assert np.isnan(result[0])
56065614
else:
56075615
# compare them as float, as these values anyhow have to under double's range to work

0 commit comments

Comments
 (0)