Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 63 additions & 13 deletions quaddtype/numpy_quaddtype/src/scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,39 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
Py_DECREF(self);
return NULL;
}
long long lval = PyLong_AsLongLong(py_int);
Py_DECREF(py_int);

if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_cast_from_int64q1(lval);
int overflow = 0;
long long lval = PyLong_AsLongLongAndOverflow(py_int, &overflow);

if (overflow != 0)
{
// Integer is too large, convert to string and recursively call this function
PyObject *str_obj = PyObject_Str(py_int);
Py_DECREF(py_int);
if (str_obj == NULL) {
Py_DECREF(self);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can test this error case by subclassing e.g. np.int64 with a __str__ implementation that raises

return NULL;
}

// Recursively convert from string
QuadPrecisionObject *result = QuadPrecision_from_object(str_obj, backend);
Py_DECREF(str_obj);
Py_DECREF(self); // discard the default one
return result;
}
else if (lval == -1 && PyErr_Occurred()) {
Py_DECREF(py_int);
Py_DECREF(self);
return NULL;
Copy link
Member

@ngoldbaum ngoldbaum Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can test this error case by defining a class with an __index__ implementation that raises.

}
else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both of the two if and else if blocks above return unconditionally, so you can delete this else block and put the code below in the enclosing scope. Just a teeny bit easier to read that way.

self->value.longdouble_value = (long double)lval;
Py_DECREF(py_int);
if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_cast_from_int64q1(lval);
}
else {
self->value.longdouble_value = (long double)lval;
}
}
return self;
}
Expand All @@ -94,9 +119,16 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
Py_DECREF(self);
return NULL;
}

// Booleans are always 0 or 1, so no overflow check needed
long long lval = PyLong_AsLongLong(py_int);
Py_DECREF(py_int);

if (lval == -1 && PyErr_Occurred()) {
Py_DECREF(self);
return NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I don't think it's feasible to hit this error case in practice because for whatever reason it's not possible to subclass np.bool_:

>>> class mybool(np.bool_):
...     pass
...
>>> mybool(True)
np.True_
>>> type(mybool(True))
<class 'numpy.bool'>

Still, always check for errors in C 😄

}

if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_cast_from_int64q1(lval);
}
Expand Down Expand Up @@ -145,7 +177,7 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
self->value.longdouble_value = (long double)dval;
}
}
else if (PyUnicode_CheckExact(value)) {
else if (PyUnicode_Check(value)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super minor nit: can you add a test passing a string subclass in to test this change?

const char *s = PyUnicode_AsUTF8(value);
char *endptr = NULL;
if (backend == BACKEND_SLEEF) {
Expand All @@ -161,17 +193,35 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
}
}
else if (PyLong_Check(value)) {
long long val = PyLong_AsLongLong(value);
if (val == -1 && PyErr_Occurred()) {
PyErr_SetString(PyExc_OverflowError, "Overflow Error, value out of range");
int overflow = 0;
long long val = PyLong_AsLongLongAndOverflow(value, &overflow);

if (overflow != 0) {
// Integer is too large, convert to string and recursively call this function
PyObject *str_obj = PyObject_Str(value);
if (str_obj == NULL) {
Py_DECREF(self);
return NULL;
}

// Recursively convert from string
QuadPrecisionObject *result = QuadPrecision_from_object(str_obj, backend);
Py_DECREF(str_obj);
Py_DECREF(self); // discard the default one
return result;
}
else if (val == -1 && PyErr_Occurred()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is identical to code added above. Can you add a quad_from_py_int static helper function in this file that both call sites can use to avoid the duplication?

Py_DECREF(self);
return NULL;
}
if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_cast_from_int64q1(val);
}
else {
self->value.longdouble_value = (long double)val;
// No overflow, use the integer value directly
if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_cast_from_int64q1(val);
}
else {
self->value.longdouble_value = (long double)val;
}
}
}
else if (Py_TYPE(value) == &QuadPrecision_Type) {
Expand Down
39 changes: 39 additions & 0 deletions quaddtype/tests/test_quaddtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,45 @@ def test_create_scalar_simple():
assert isinstance(QuadPrecision(1), QuadPrecision)


@pytest.mark.parametrize("int_val", [
# Very large integers that exceed long double range
2 ** 1024,
2 ** 2048,
10 ** 308,
10 ** 4000,
# Edge cases
0,
1,
-1,
# Negative large integers
-(2 ** 1024),
])
def test_create_scalar_from_large_int(int_val):
"""Test that QuadPrecision can handle very large integers beyond long double range.

This test ensures that integers like 2**1024, which overflow standard long double,
are properly converted via string representation to QuadPrecision without raising
overflow errors. The conversion should match the string-based conversion.
"""
# Convert large int to QuadPrecision
result = QuadPrecision(int_val)
assert isinstance(result, QuadPrecision)

# String conversion should give the same result
str_val = str(int_val)
result_from_str = QuadPrecision(str_val)

# Both conversions should produce the same value
# (can be inf==inf on some platforms for very large values)
assert result == result_from_str

# For zero and small values, verify exact conversion
if int_val == 0:
assert float(result) == 0.0
elif abs(int_val) == 1:
assert float(result) == float(int_val)


class TestQuadPrecisionArrayCreation:
"""Test suite for QuadPrecision array creation from sequences and arrays."""

Expand Down