Skip to content

Commit 3164b96

Browse files
authored
Merge pull request numpy#102 from SwayamInSync/98-fix
Fix numpy#98: Return 0 when casting NaN QuadPrecision to integer types
2 parents fc52921 + 20751d9 commit 3164b96

2 files changed

Lines changed: 58 additions & 3 deletions

File tree

src/csrc/casts.cpp

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,13 +1223,25 @@ from_quad<spec_npy_bool>(const quad_value *x, QuadBackendType backend)
12231223
}
12241224
}
12251225

1226+
// NumPy convention: casting NaN to any integer type yields 0.
1227+
// Without this, SLEEF saturates NaN to INT_MAX / UINT_MAX, and the
1228+
// long-double path invokes undefined behavior. See gh-98.
1229+
static inline bool
1230+
quad_is_nan(const quad_value *x, QuadBackendType backend)
1231+
{
1232+
if (backend == BACKEND_SLEEF) {
1233+
return quad_isnan(&x->sleef_value);
1234+
}
1235+
return std::isnan(x->longdouble_value);
1236+
}
1237+
12261238
template <>
12271239
inline npy_byte
12281240
from_quad<npy_byte>(const quad_value *x, QuadBackendType backend)
12291241
{
1230-
// runtime warnings often comes from/to casting of NaN, inf
1231-
// casting is used by ops at several positions leading to warnings
1232-
// fix can be catching the cases and returning corresponding type value without casting
1242+
if (quad_is_nan(x, backend)) {
1243+
return (npy_byte)0;
1244+
}
12331245
if (backend == BACKEND_SLEEF) {
12341246
return (npy_byte)Sleef_cast_to_int64q1(x->sleef_value);
12351247
}
@@ -1242,6 +1254,9 @@ template <>
12421254
inline npy_ubyte
12431255
from_quad<npy_ubyte>(const quad_value *x, QuadBackendType backend)
12441256
{
1257+
if (quad_is_nan(x, backend)) {
1258+
return (npy_ubyte)0;
1259+
}
12451260
if (backend == BACKEND_SLEEF) {
12461261
return (npy_ubyte)Sleef_cast_to_uint64q1(x->sleef_value);
12471262
}
@@ -1254,6 +1269,9 @@ template <>
12541269
inline npy_short
12551270
from_quad<npy_short>(const quad_value *x, QuadBackendType backend)
12561271
{
1272+
if (quad_is_nan(x, backend)) {
1273+
return (npy_short)0;
1274+
}
12571275
if (backend == BACKEND_SLEEF) {
12581276
return (npy_short)Sleef_cast_to_int64q1(x->sleef_value);
12591277
}
@@ -1266,6 +1284,9 @@ template <>
12661284
inline npy_ushort
12671285
from_quad<npy_ushort>(const quad_value *x, QuadBackendType backend)
12681286
{
1287+
if (quad_is_nan(x, backend)) {
1288+
return (npy_ushort)0;
1289+
}
12691290
if (backend == BACKEND_SLEEF) {
12701291
return (npy_ushort)Sleef_cast_to_uint64q1(x->sleef_value);
12711292
}
@@ -1278,6 +1299,9 @@ template <>
12781299
inline npy_int
12791300
from_quad<npy_int>(const quad_value *x, QuadBackendType backend)
12801301
{
1302+
if (quad_is_nan(x, backend)) {
1303+
return (npy_int)0;
1304+
}
12811305
if (backend == BACKEND_SLEEF) {
12821306
return (npy_int)Sleef_cast_to_int64q1(x->sleef_value);
12831307
}
@@ -1290,6 +1314,9 @@ template <>
12901314
inline npy_uint
12911315
from_quad<npy_uint>(const quad_value *x, QuadBackendType backend)
12921316
{
1317+
if (quad_is_nan(x, backend)) {
1318+
return (npy_uint)0;
1319+
}
12931320
if (backend == BACKEND_SLEEF) {
12941321
return (npy_uint)Sleef_cast_to_uint64q1(x->sleef_value);
12951322
}
@@ -1302,6 +1329,9 @@ template <>
13021329
inline npy_long
13031330
from_quad<npy_long>(const quad_value *x, QuadBackendType backend)
13041331
{
1332+
if (quad_is_nan(x, backend)) {
1333+
return (npy_long)0;
1334+
}
13051335
if (backend == BACKEND_SLEEF) {
13061336
return (npy_long)Sleef_cast_to_int64q1(x->sleef_value);
13071337
}
@@ -1314,6 +1344,9 @@ template <>
13141344
inline npy_ulong
13151345
from_quad<npy_ulong>(const quad_value *x, QuadBackendType backend)
13161346
{
1347+
if (quad_is_nan(x, backend)) {
1348+
return (npy_ulong)0;
1349+
}
13171350
if (backend == BACKEND_SLEEF) {
13181351
return (npy_ulong)Sleef_cast_to_uint64q1(x->sleef_value);
13191352
}
@@ -1326,6 +1359,9 @@ template <>
13261359
inline npy_longlong
13271360
from_quad<npy_longlong>(const quad_value *x, QuadBackendType backend)
13281361
{
1362+
if (quad_is_nan(x, backend)) {
1363+
return (npy_longlong)0;
1364+
}
13291365
if (backend == BACKEND_SLEEF) {
13301366
return Sleef_cast_to_int64q1(x->sleef_value);
13311367
}
@@ -1338,6 +1374,9 @@ template <>
13381374
inline npy_ulonglong
13391375
from_quad<npy_ulonglong>(const quad_value *x, QuadBackendType backend)
13401376
{
1377+
if (quad_is_nan(x, backend)) {
1378+
return (npy_ulonglong)0;
1379+
}
13411380
if (backend == BACKEND_SLEEF) {
13421381
return Sleef_cast_to_uint64q1(x->sleef_value);
13431382
}

tests/test_quaddtype.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,22 @@ def test_supported_astype(dtype):
541541
assert back == orig
542542

543543

544+
@pytest.mark.parametrize("backend", ["sleef", "longdouble"])
545+
@pytest.mark.parametrize("nan_str", ["nan", "-nan"])
546+
@pytest.mark.parametrize("dtype", [
547+
"byte", "int8", "ubyte", "uint8",
548+
"short", "int16", "ushort", "uint16",
549+
"int", "int32", "uint", "uint32",
550+
"long", "ulong",
551+
"longlong", "int64", "ulonglong", "uint64",
552+
])
553+
def test_astype_nan_to_int_is_zero(dtype, nan_str, backend):
554+
"""NaN cast to any integer type must yield 0 (NumPy convention). Regression: gh-98."""
555+
arr = np.array([QuadPrecision(nan_str, backend=backend)])
556+
result = arr.astype(dtype)
557+
assert result[0] == 0
558+
559+
544560
@pytest.mark.parametrize("dtype", ["V10", "datetime64[ms]", "timedelta64[ms]"])
545561
def test_unsupported_astype(dtype):
546562
if dtype == "V10":

0 commit comments

Comments
 (0)