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
2 changes: 1 addition & 1 deletion .github/workflows/big_endian.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ permissions:

jobs:
big_endian_tests:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
continue-on-error: true
strategy:
fail-fast: false
Expand Down
16 changes: 13 additions & 3 deletions quaddtype/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ if sleef_dep.found() and sleef_dep.type_name() != 'internal'
# SLEEF found system-wide - verify quad-precision support
cpp = meson.get_compiler('cpp')
sleefquad_lib = cpp.find_library('sleefquad', required: false)
tlfloat_lib = cpp.find_library('tlfloat', required: false)

if sleefquad_lib.found()
if sleefquad_lib.found() and tlfloat_lib.found()
sleefquad_test_code = '''
#include <sleefquad.h>

Expand All @@ -40,14 +41,17 @@ if sleef_dep.found() and sleef_dep.type_name() != 'internal'
# this should compile and link
quad_works = cpp.links(
sleefquad_test_code,
dependencies: [sleef_dep, sleefquad_lib],
dependencies: [sleef_dep, sleefquad_lib, tlfloat_lib],
name: 'SLEEF quad-precision support'
)

if quad_works
sleefquad_dep = declare_dependency(
dependencies: [sleef_dep, sleefquad_lib]
)
tlfloat_dep = declare_dependency(
dependencies: [tlfloat_lib]
)
use_system_sleef = true
else
fallback_reason = 'System-wide SLEEF installation found but a test for quad precision support failed.'
Expand All @@ -65,6 +69,7 @@ else
sleef_subproj = subproject('sleef')
sleef_dep = sleef_subproj.get_variable('sleef_dep')
sleefquad_dep = sleef_subproj.get_variable('sleefquad_dep')
tlfloat_dep = sleef_subproj.get_variable('tlfloat_dep')
warning(fallback_reason)
message('Proceeding with vendored SLEEF subproject instead')
endif
Expand All @@ -84,7 +89,7 @@ message('Using NumPy version: @0@'.format(numpy_version))
npymath_path = incdir_numpy / '..' / 'lib'
npymath_lib = c.find_library('npymath', dirs: npymath_path)

dependencies = [py_dep, qblas_dep, sleef_dep, sleefquad_dep, npymath_lib]
dependencies = [py_dep, qblas_dep, sleef_dep, sleefquad_dep, tlfloat_dep, npymath_lib]

# Add OpenMP dependency (optional, for threading)
openmp_dep = dependency('openmp', required: false, static: false)
Expand All @@ -99,6 +104,11 @@ if not is_windows
if cpp.has_argument('-fext-numeric-literals')
add_project_arguments('-fext-numeric-literals', language: 'cpp')
endif

# Suppress warnings from system headers (sleefquad.h has many unused inline functions)
if cpp.has_argument('-Wno-unused-function')
add_project_arguments('-Wno-unused-function', language: ['c', 'cpp'])
endif
endif

# Thread-local storage detection (borrowed from NumPy)
Expand Down
9 changes: 5 additions & 4 deletions quaddtype/numpy_quaddtype/src/dragon4.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ Modifications are specific to support the SLEEF_QUAD
#include "scalar.h"


// Undefine NPY_TLS if already defined (avoid redefinition warning)
#ifdef NPY_TLS
#undef NPY_TLS
#endif

#ifdef __cplusplus
#define NPY_TLS thread_local
#elif defined(HAVE_THREAD_LOCAL)
Expand Down Expand Up @@ -2005,8 +2010,6 @@ PyObject *
Dragon4_Positional(PyObject *obj, DigitMode digit_mode, CutoffMode cutoff_mode, int precision,
int min_digits, int sign, TrimMode trim, int pad_left, int pad_right)
{
npy_double v;

if (PyObject_TypeCheck(obj, &QuadPrecision_Type)) {
QuadPrecisionObject *quad_obj = (QuadPrecisionObject *)obj;
if (quad_obj->backend == BACKEND_SLEEF) {
Expand All @@ -2028,8 +2031,6 @@ PyObject *
Dragon4_Scientific(PyObject *obj, DigitMode digit_mode, int precision, int min_digits, int sign,
TrimMode trim, int pad_left, int exp_digits)
{
npy_double val;

if (PyObject_TypeCheck(obj, &QuadPrecision_Type)) {
QuadPrecisionObject *quad_obj = (QuadPrecisionObject *)obj;
if (quad_obj->backend == BACKEND_SLEEF) {
Expand Down
8 changes: 4 additions & 4 deletions quaddtype/numpy_quaddtype/src/ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ typedef Sleef_quad (*unary_op_quad_def)(const Sleef_quad *);
// Unary Quad operations with 2 outputs (for modf, frexp)
typedef void (*unary_op_2out_quad_def)(const Sleef_quad *, Sleef_quad *, Sleef_quad *);

static Sleef_quad
[[maybe_unused]] static Sleef_quad
quad_negative(const Sleef_quad *op)
{
return Sleef_negq1(*op);
}

static Sleef_quad
[[maybe_unused]] static Sleef_quad
quad_positive(const Sleef_quad *op)
{
return *op;
Expand Down Expand Up @@ -929,15 +929,15 @@ static inline Sleef_quad quad_set_words64(int64_t hx, uint64_t lx)
static inline Sleef_quad
quad_nextafter(const Sleef_quad *x, const Sleef_quad *y)
{
int64_t hx, hy, ix, iy;
int64_t hx, hy, ix;
uint64_t lx, ly;

quad_get_words64(&hx, &lx, *x);
quad_get_words64(&hy, &ly, *y);

// extracting absolute value
ix = hx & 0x7fffffffffffffffLL;
iy = hy & 0x7fffffffffffffffLL;
(void)ly; // unused but needed for quad_get_words64
Copy link
Member

Choose a reason for hiding this comment

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

ly is used below, on line 960

Copy link
Member Author

Choose a reason for hiding this comment

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

That just a read, so clang gives warnings, so just adding temporary void casting


// NaN if either is NaN
if (Sleef_iunordq1(*x, *y)) {
Expand Down
35 changes: 20 additions & 15 deletions quaddtype/numpy_quaddtype/src/scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
else if (PyUnicode_Check(value)) {
const char *s = PyUnicode_AsUTF8(value);
char *endptr = NULL;
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
return NULL;
}
// Skip trailing whitespace (matches Python's float() behavior)
while (ascii_isspace(*endptr)) {
endptr++;
}
if (*endptr != '\0') {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
return NULL;
}
}
else if (PyBytes_Check(value)) {
const char *s = PyBytes_AsString(value);
Expand All @@ -205,12 +214,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
return NULL;
}
char *endptr = NULL;
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
int err = NumPyOS_ascii_strtoq(s, backend, &self->value, &endptr);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
return NULL;
}
// Skip trailing whitespace (matches Python's float() behavior)
while (ascii_isspace(*endptr)) {
endptr++;
}
if (*endptr != '\0') {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
return NULL;
}
}
else if (PyLong_Check(value)) {
return quad_from_py_int(value, backend, self);
Expand Down Expand Up @@ -320,19 +338,6 @@ QuadPrecision_str(QuadPrecisionObject *self)
return PyUnicode_FromString(buffer);
}

static PyObject *
QuadPrecision_repr(QuadPrecisionObject *self)
{
PyObject *str = QuadPrecision_str(self);
if (str == NULL) {
return NULL;
}
const char *backend_str = (self->backend == BACKEND_SLEEF) ? "sleef" : "longdouble";
PyObject *res = PyUnicode_FromFormat("QuadPrecision('%S', backend='%s')", str, backend_str);
Py_DECREF(str);
return res;
}

static PyObject *
QuadPrecision_repr_dragon4(QuadPrecisionObject *self)
{
Expand Down
54 changes: 0 additions & 54 deletions quaddtype/numpy_quaddtype/src/umath/umath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,60 +25,6 @@ extern "C" {
#include "comparison_ops.h"
#include "matmul.h"

// helper debugging function
static const char *
get_dtype_name(PyArray_DTypeMeta *dtype)
{
if (dtype == &QuadPrecDType) {
return "QuadPrecDType";
}
else if (dtype == &PyArray_BoolDType) {
return "BoolDType";
}
else if (dtype == &PyArray_ByteDType) {
return "ByteDType";
}
else if (dtype == &PyArray_UByteDType) {
return "UByteDType";
}
else if (dtype == &PyArray_ShortDType) {
return "ShortDType";
}
else if (dtype == &PyArray_UShortDType) {
return "UShortDType";
}
else if (dtype == &PyArray_IntDType) {
return "IntDType";
}
else if (dtype == &PyArray_UIntDType) {
return "UIntDType";
}
else if (dtype == &PyArray_LongDType) {
return "LongDType";
}
else if (dtype == &PyArray_ULongDType) {
return "ULongDType";
}
else if (dtype == &PyArray_LongLongDType) {
return "LongLongDType";
}
else if (dtype == &PyArray_ULongLongDType) {
return "ULongLongDType";
}
else if (dtype == &PyArray_FloatDType) {
return "FloatDType";
}
else if (dtype == &PyArray_DoubleDType) {
return "DoubleDType";
}
else if (dtype == &PyArray_LongDoubleDType) {
return "LongDoubleDType";
}
else {
return "UnknownDType";
}
}

int
init_quad_umath(void)
{
Expand Down
100 changes: 94 additions & 6 deletions quaddtype/numpy_quaddtype/src/utilities.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,17 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val
}
}

// Set NaN value (sign is ignored for NaN)
// Set NaN value with sign preserved
if (backend == BACKEND_SLEEF) {
out_value->sleef_value = QUAD_PRECISION_NAN;
Sleef_quad nan_val = QUAD_PRECISION_NAN;
// Apply sign to NaN (negative NaN has sign bit set)
if (sign < 0) {
nan_val = Sleef_negq1(nan_val);
}
out_value->sleef_value = nan_val;
}
else {
out_value->longdouble_value = nanl("");
out_value->longdouble_value = sign < 0 ? -nanl("") : nanl("");
}

if (endptr) {
Expand All @@ -157,15 +162,98 @@ int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_va
char **endptr, bool require_full_parse)
{
if(backend == BACKEND_SLEEF) {
out_value->sleef_value = Sleef_strtoq(str, endptr);
// SLEEF 4.0's Sleef_strtoq doesn't properly set endptr to indicate
Copy link
Member

Choose a reason for hiding this comment

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

kinda seems like a SLEEF bug you should report (even if they'll ignore it).

// where parsing stopped. It always sets endptr to the end of the string.
// We need to manually validate and track the parse position.

const char *p = str;

// Skip leading whitespace
while (ascii_isspace(*p)) {
p++;
}

// Handle optional sign
if (*p == '+' || *p == '-') {
p++;
}

// Must have at least one digit or decimal point followed by digit
int has_digits = 0;

// Parse integer part
while (ascii_isdigit(*p)) {
has_digits = 1;
p++;
}

// Parse decimal point and fractional part
if (*p == '.') {
p++;
while (ascii_isdigit(*p)) {
has_digits = 1;
p++;
}
}

// Must have at least one digit somewhere
if (!has_digits) {
if (endptr) *endptr = (char *)str;
return -1;
}

// Parse optional exponent
if (*p == 'e' || *p == 'E') {
const char *exp_start = p;
p++;

// Optional sign in exponent
if (*p == '+' || *p == '-') {
p++;
}

// Must have at least one digit in exponent
if (!ascii_isdigit(*p)) {
// Invalid exponent, backtrack
p = exp_start;
} else {
while (ascii_isdigit(*p)) {
p++;
}
}
}

// Now p points to where valid parsing ends
// SLEEF 4.0's Sleef_strtoq has a bug where it doesn't properly stop at whitespace
// or other delimiters. We need to create a null-terminated substring.
size_t len = p - str;
char *temp = (char *)malloc(len + 1);
if (!temp) {
if (endptr) *endptr = (char *)str;
return -1;
}
memcpy(temp, str, len);
temp[len] = '\0';

// Call Sleef_strtoq with the bounded string
char *sleef_endptr;
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
Copy link
Member

Choose a reason for hiding this comment

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

this is pretty subuptimal :(

Consider refactoring all the code you added here into a function even if there's only one caller, since it's pretty distracting here inline in this function.

free(temp);

// Set endptr to our calculated position
if (endptr) {
*endptr = (char *)p;
}

} else {
out_value->longdouble_value = strtold(str, endptr);
}
if(*endptr == str)

if(endptr && *endptr == str)
return -1; // parse error - nothing was parsed

// If full parse is required
if(require_full_parse && **endptr != '\0')
if(require_full_parse && endptr && **endptr != '\0')
return -1; // parse error - characters remain to be converted

return 0; // success
Expand Down
11 changes: 5 additions & 6 deletions quaddtype/reinstall.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
#!/bin/bash
set -x

if [ -d "build/" ]; then
rm -r build
rm -rf dist/
rm -rf subprojects/qblas
rm -rf subprojects/sleef
fi
rm -rf build/
rm -rf dist/
rm -rf subprojects/qblas
rm -rf subprojects/sleef
rm -rf .mesonpy-*

python -m pip uninstall -y numpy_quaddtype
python -m pip install . -vv --no-build-isolation 2>&1 | tee build_log.txt
Expand Down
Loading
Loading