Skip to content

Commit 8b305c9

Browse files
authored
Merge pull request #104 from SwayamInSync/fix-int-conversion-97
Fix `int(QuadPrecision)` for NaN, Inf, and out-of-int64 values
2 parents 60b1222 + 24990a7 commit 8b305c9

5 files changed

Lines changed: 292 additions & 16 deletions

File tree

.github/workflows/test_old_cpu.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ jobs:
3939

4040
- name: Install Intel SDE
4141
run: |
42-
curl -o /tmp/sde.tar.xz https://downloadmirror.intel.com/859732/sde-external-9.58.0-2025-06-16-lin.tar.xz
42+
SDE_URL="https://github.com/SwayamInSync/numpy-quaddtype/releases/download/sde-toolchain/sde-external-10.8.0-2026-03-15-lin.tar.xz"
43+
SDE_SHA256="50b320cd226acef7a491f5b321fc1be3c3c7984f9e27a456e64894b5b0979dd3"
44+
curl -fSL -o /tmp/sde.tar.xz "$SDE_URL"
45+
echo "$SDE_SHA256 /tmp/sde.tar.xz" | sha256sum -c -
4346
mkdir /tmp/sde && tar -xvf /tmp/sde.tar.xz -C /tmp/sde/
4447
sudo mv /tmp/sde/* /opt/sde && sudo ln -s /opt/sde/sde64 /usr/bin/sde
4548

src/csrc/scalar.c

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <sleef.h>
33
#include <sleefquad.h>
44
#include <stdlib.h>
5+
#include <math.h>
56

67
#define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API
78
#define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION
@@ -21,6 +22,11 @@
2122
#include "pythoncapi_compat.h"
2223

2324

25+
// forward declaration
26+
static Sleef_quad
27+
longdouble_to_quad(long double value);
28+
29+
2430
QuadPrecisionObject *
2531
QuadPrecision_raw_new(QuadBackendType backend)
2632
{
@@ -443,8 +449,7 @@ QuadPrecision_is_integer(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored)
443449
value = self->value.sleef_value;
444450
}
445451
else {
446-
// lets also tackle ld from sleef functions as well
447-
value = Sleef_cast_from_doubleq1((double)self->value.longdouble_value);
452+
value = longdouble_to_quad(self->value.longdouble_value);
448453
}
449454

450455
if(Sleef_iunordq1(value, value)) {
@@ -462,9 +467,7 @@ QuadPrecision_is_integer(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored)
462467

463468
// Check if value equals its truncated version
464469
Sleef_quad truncated = Sleef_truncq1(value);
465-
int32_t is_equal = Sleef_icmpeqq1(value, truncated);
466-
467-
if (is_equal) {
470+
if (Sleef_icmpeqq1(value, truncated)) {
468471
Py_RETURN_TRUE;
469472
}
470473
else {
@@ -474,7 +477,7 @@ QuadPrecision_is_integer(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored)
474477

475478
PyObject* quad_to_pylong(Sleef_quad value)
476479
{
477-
char buffer[128];
480+
char buffer[4936]; // 4933 + sign + null terminator, enough for 128-bit integer in decimal
478481

479482
// Sleef_snprintf call is thread-unsafe
480483
LOCK_SLEEF;
@@ -492,6 +495,37 @@ PyObject* quad_to_pylong(Sleef_quad value)
492495
return result;
493496
}
494497

498+
PyObject* longdouble_to_pylong(long double value)
499+
{
500+
char buffer[4936]; // 4933 + sign + null terminator, enough for 128-bit integer in decimal
501+
502+
// POSIX guarantees thread-safety of snprintf
503+
int written = snprintf(buffer, sizeof(buffer), "%.0Lf", value);
504+
if (written < 0 || written >= sizeof(buffer)) {
505+
PyErr_SetString(PyExc_RuntimeError, "Failed to convert long double to string");
506+
return NULL;
507+
}
508+
509+
// Already raises ValueError and returns NULL on failure
510+
return PyLong_FromString(buffer, NULL, 10);
511+
}
512+
513+
static Sleef_quad
514+
longdouble_to_quad(long double value)
515+
{
516+
if (isnan(value) || isinf(value) || value == 0.0L)
517+
return Sleef_cast_from_doubleq1((double)value);
518+
519+
int exp;
520+
long double mantissa = frexpl(value, &exp);
521+
long double scaled = ldexpl(mantissa, 64);
522+
exp -= 64;
523+
Sleef_quad q = (scaled < 0)
524+
? Sleef_negq1(Sleef_cast_from_uint64q1((uint64_t)(-scaled)))
525+
: Sleef_cast_from_uint64q1((uint64_t)scaled);
526+
return Sleef_ldexpq1(q, exp);
527+
}
528+
495529
// inspired by the CPython implementation
496530
// https://github.com/python/cpython/blob/ac1ffd77858b62d169a08040c08aa5de26e145ac/Objects/floatobject.c#L1503C1-L1572C2
497531
static PyObject *
@@ -504,10 +538,8 @@ QuadPrecision_as_integer_ratio(QuadPrecisionObject *self, PyObject *Py_UNUSED(ig
504538

505539
if (self->backend == BACKEND_SLEEF) {
506540
value = self->value.sleef_value;
507-
}
508-
else {
509-
// lets also tackle ld from sleef functions as well
510-
value = Sleef_cast_from_doubleq1((double)self->value.longdouble_value);
541+
} else {
542+
value = longdouble_to_quad(self->value.longdouble_value);
511543
}
512544

513545
if(Sleef_iunordq1(value, value)) {
@@ -653,7 +685,7 @@ QuadPrecision_hash(QuadPrecisionObject *self)
653685
value = self->value.sleef_value;
654686
}
655687
else {
656-
value = Sleef_cast_from_doubleq1((double)self->value.longdouble_value);
688+
value = longdouble_to_quad(self->value.longdouble_value);
657689
}
658690

659691
// Check for NaN - use pointer hash (each NaN instance gets unique hash)

src/csrc/scalar_ops.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#define NPY_TARGET_VERSION NPY_2_4_API_VERSION
44
#define NO_IMPORT_ARRAY
55

6+
#include <cmath>
7+
68
extern "C" {
79
#include <Python.h>
810

@@ -224,12 +226,35 @@ QuadPrecision_float(QuadPrecisionObject *self)
224226
static PyObject *
225227
QuadPrecision_int(QuadPrecisionObject *self)
226228
{
227-
if (self->backend == BACKEND_SLEEF) {
228-
return PyLong_FromLongLong(Sleef_cast_to_int64q1(self->value.sleef_value));
229+
if (self->backend == BACKEND_SLEEF)
230+
{
231+
Sleef_quad value = self->value.sleef_value;
232+
if (quad_isnan(&value)) {
233+
PyErr_SetString(PyExc_ValueError, "cannot convert float NaN to integer");
234+
return NULL;
229235
}
230-
else {
231-
return PyLong_FromLongLong((long long)self->value.longdouble_value);
236+
if (quad_isinf(&value))
237+
{
238+
PyErr_SetString(PyExc_OverflowError,
239+
"cannot convert float infinity to integer");
240+
return NULL;
241+
}
242+
return quad_to_pylong(Sleef_truncq1(value));
243+
244+
}
245+
246+
long double value = self->value.longdouble_value;
247+
if(std::isnan(value))
248+
{
249+
PyErr_SetString(PyExc_ValueError, "cannot convert float NaN to integer");
250+
return NULL;
251+
}
252+
if(std::isinf(value))
253+
{
254+
PyErr_SetString(PyExc_OverflowError, "cannot convert float infinity to integer");
255+
return NULL;
232256
}
257+
return longdouble_to_pylong(truncl(value));
233258
}
234259

235260
template <binary_op_quad_def sleef_op, binary_op_longdouble_def longdouble_op>

src/include/scalar.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern "C" {
77

88
#include <Python.h>
99
#include <sleef.h>
10+
#include <sleefquad.h>
1011
#include "quad_common.h"
1112

1213
typedef struct {
@@ -26,6 +27,11 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend);
2627
int
2728
init_quadprecision_scalar(void);
2829

30+
PyObject *
31+
quad_to_pylong(Sleef_quad value);
32+
PyObject *
33+
longdouble_to_pylong(long double value);
34+
2935
#ifdef __cplusplus
3036
}
3137
#endif

0 commit comments

Comments
 (0)