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
4 changes: 4 additions & 0 deletions quaddtype/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ srcs = [
'numpy_quaddtype/src/umath/matmul.h',
'numpy_quaddtype/src/umath/matmul.cpp',
'numpy_quaddtype/src/constants.hpp',
'numpy_quaddtype/src/lock.h',
'numpy_quaddtype/src/lock.c',
'numpy_quaddtype/src/utilities.h',
'numpy_quaddtype/src/utilities.c',
]

py.install_sources(
Expand Down
1 change: 1 addition & 0 deletions quaddtype/numpy_quaddtype/_quaddtype_main.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ _IntoQuad: TypeAlias = (
QuadPrecision
| float
| str
| bytes
| np.floating[Any]
| np.integer[Any]
| np.bool_
Expand Down
2 changes: 1 addition & 1 deletion quaddtype/numpy_quaddtype/src/casts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,7 @@ init_casts_internal(void)
add_spec(quad2quad_spec);

PyArray_DTypeMeta **void_dtypes = new PyArray_DTypeMeta *[2]{&PyArray_VoidDType, &QuadPrecDType};
PyType_Slot *void_slots = new PyType_Slot[]{
PyType_Slot *void_slots = new PyType_Slot[4]{
{NPY_METH_resolve_descriptors, (void *)&void_to_quad_resolve_descriptors},
{NPY_METH_strided_loop, (void *)&void_to_quad_strided_loop},
{NPY_METH_unaligned_strided_loop, (void *)&void_to_quad_strided_loop},
Expand Down
40 changes: 17 additions & 23 deletions quaddtype/numpy_quaddtype/src/dtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
#include "numpy/ndarraytypes.h"
#include "numpy/dtype_api.h"

#include "quad_common.h"
#include "scalar.h"
#include "casts.h"
#include "dtype.h"
#include "dragon4.h"
#include "constants.hpp"
#include "utilities.h"

static inline int
quad_load(void *x, char *data_ptr, QuadBackendType backend)
Expand Down Expand Up @@ -353,19 +355,16 @@ quadprec_scanfunc(FILE *fp, void *dptr, char *ignore, PyArray_Descr *descr_gener

/* Convert string to quad precision */
char *endptr;
quad_value val;
int err = cstring_to_quad(buffer, descr->backend, &val, &endptr, true);
if (err < 0) {
return 0; /* Return 0 on parse error (no items read) */
}
if (descr->backend == BACKEND_SLEEF) {
Sleef_quad val = Sleef_strtoq(buffer, &endptr);
if (endptr == buffer) {
return 0; /* Return 0 on parse error (no items read) */
}
*(Sleef_quad *)dptr = val;
*(Sleef_quad *)dptr = val.sleef_value;
}
else {
long double val = strtold(buffer, &endptr);
if (endptr == buffer) {
return 0; /* Return 0 on parse error (no items read) */
}
*(long double *)dptr = val;
*(long double *)dptr = val.longdouble_value;
}

return 1; /* Return 1 on success (1 item read) */
Expand All @@ -375,22 +374,17 @@ static int
quadprec_fromstr(char *s, void *dptr, char **endptr, PyArray_Descr *descr_generic)
{
QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)descr_generic;

if (descr->backend == BACKEND_SLEEF) {
Sleef_quad val = Sleef_strtoq(s, endptr);
if (*endptr == s) {
return -1;
}
*(Sleef_quad *)dptr = val;
quad_value val;
int err = cstring_to_quad(s, descr->backend, &val, endptr, false);
if (err < 0) {
return -1;
}
if(descr->backend == BACKEND_SLEEF) {
*(Sleef_quad *)dptr = val.sleef_value;
}
else {
long double val = strtold(s, endptr);
if (*endptr == s) {
return -1;
}
*(long double *)dptr = val;
*(long double *)dptr = val.longdouble_value;
}

return 0;
}

Expand Down
17 changes: 17 additions & 0 deletions quaddtype/numpy_quaddtype/src/lock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "lock.h"

#if PY_VERSION_HEX < 0x30d00b3
PyThread_type_lock sleef_lock = NULL;
#else
PyMutex sleef_lock = {0};
#endif

void init_sleef_locks(void)
{
#if PY_VERSION_HEX < 0x30d00b3
sleef_lock = PyThread_allocate_lock();
if (!sleef_lock) {
PyErr_NoMemory();
}
#endif
}
18 changes: 18 additions & 0 deletions quaddtype/numpy_quaddtype/src/lock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef _QUADDTYPE_LOCK_H
#define _QUADDTYPE_LOCK_H

#include <Python.h>

#if PY_VERSION_HEX < 0x30d00b3
extern PyThread_type_lock sleef_lock;
#define LOCK_SLEEF PyThread_acquire_lock(sleef_lock, WAIT_LOCK)
#define UNLOCK_SLEEF PyThread_release_lock(sleef_lock)
#else
extern PyMutex sleef_lock;
#define LOCK_SLEEF PyMutex_Lock(&sleef_lock)
#define UNLOCK_SLEEF PyMutex_Unlock(&sleef_lock)
#endif

void init_sleef_locks(void);

#endif // _QUADDTYPE_LOCK_H
8 changes: 8 additions & 0 deletions quaddtype/numpy_quaddtype/src/quad_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
extern "C" {
#endif

#include <sleef.h>
#include <sleefquad.h>

typedef enum {
BACKEND_INVALID = -1,
BACKEND_SLEEF,
BACKEND_LONGDOUBLE
} QuadBackendType;

typedef union {
Sleef_quad sleef_value;
long double longdouble_value;
} quad_value;

#ifdef __cplusplus
}
#endif
Expand Down
3 changes: 3 additions & 0 deletions quaddtype/numpy_quaddtype/src/quaddtype_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "numpy/dtype_api.h"
#include "numpy/ufuncobject.h"

#include "lock.h"
#include "scalar.h"
#include "dtype.h"
#include "umath/umath.h"
Expand Down Expand Up @@ -96,6 +97,8 @@ PyInit__quaddtype_main(void)
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif

init_sleef_locks();

if (init_quadprecision_scalar() < 0)
goto error;

Expand Down
48 changes: 20 additions & 28 deletions quaddtype/numpy_quaddtype/src/scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,15 @@
#include "scalar_ops.h"
#include "dragon4.h"
#include "dtype.h"
#include "lock.h"
#include "utilities.h"

// For IEEE 754 binary128 (quad precision), we need 36 decimal digits
// to guarantee round-trip conversion (string -> parse -> equals original value)
// Formula: ceil(1 + MANT_DIG * log10(2)) = ceil(1 + 113 * 0.30103) = 36
// src: https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format
#define SLEEF_QUAD_DECIMAL_DIG 36

#if PY_VERSION_HEX < 0x30d00b3
static PyThread_type_lock sleef_lock;
#define LOCK_SLEEF PyThread_acquire_lock(sleef_lock, WAIT_LOCK)
#define UNLOCK_SLEEF PyThread_release_lock(sleef_lock)
#else
static PyMutex sleef_lock = {0};
#define LOCK_SLEEF PyMutex_Lock(&sleef_lock)
#define UNLOCK_SLEEF PyMutex_Unlock(&sleef_lock)
#endif




QuadPrecisionObject *
QuadPrecision_raw_new(QuadBackendType backend)
Expand Down Expand Up @@ -207,14 +197,23 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
else if (PyUnicode_Check(value)) {
const char *s = PyUnicode_AsUTF8(value);
char *endptr = NULL;
if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_strtoq(s, &endptr);
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
return NULL;
}
else {
self->value.longdouble_value = strtold(s, &endptr);
}
else if (PyBytes_Check(value)) {
const char *s = PyBytes_AsString(value);
if (s == NULL) {
Py_DECREF(self);
return NULL;
}
if (*endptr != '\0' || endptr == s) {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
char *endptr = NULL;
int err = cstring_to_quad(s, backend, &self->value, &endptr, true);
if (err < 0) {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
return NULL;
}
Expand Down Expand Up @@ -242,21 +241,21 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
const char *type_cstr = PyUnicode_AsUTF8(type_str);
if (type_cstr != NULL) {
PyErr_Format(PyExc_TypeError,
"QuadPrecision value must be a quad, float, int, string, array or sequence, but got %s "
"QuadPrecision value must be a quad, float, int, string, bytes, array or sequence, but got %s "
"instead",
type_cstr);
}
else {
PyErr_SetString(
PyExc_TypeError,
"QuadPrecision value must be a quad, float, int, string, array or sequence, but got an "
"QuadPrecision value must be a quad, float, int, string, bytes, array or sequence, but got an "
"unknown type instead");
}
Py_DECREF(type_str);
}
else {
PyErr_SetString(PyExc_TypeError,
"QuadPrecision value must be a quad, float, int, string, array or sequence, but got an "
"QuadPrecision value must be a quad, float, int, string, bytes, array or sequence, but got an "
"unknown type instead");
}
Py_DECREF(self);
Expand Down Expand Up @@ -636,13 +635,6 @@ PyTypeObject QuadPrecision_Type = {
int
init_quadprecision_scalar(void)
{
#if PY_VERSION_HEX < 0x30d00b3
sleef_lock = PyThread_allocate_lock();
if (sleef_lock == NULL) {
PyErr_NoMemory();
return -1;
}
#endif
QuadPrecision_Type.tp_base = &PyFloatingArrType_Type;
return PyType_Ready(&QuadPrecision_Type);
}
5 changes: 0 additions & 5 deletions quaddtype/numpy_quaddtype/src/scalar.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ extern "C" {
#include <sleef.h>
#include "quad_common.h"

typedef union {
Sleef_quad sleef_value;
long double longdouble_value;
} quad_value;

typedef struct {
PyObject_HEAD
quad_value value;
Expand Down
20 changes: 20 additions & 0 deletions quaddtype/numpy_quaddtype/src/utilities.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "utilities.h"
#include <stdlib.h>

int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value,
char **endptr, bool require_full_parse)
{
if(backend == BACKEND_SLEEF) {
out_value->sleef_value = Sleef_strtoq(str, endptr);
} else {
out_value->longdouble_value = strtold(str, endptr);
}
Copy link
Member

Choose a reason for hiding this comment

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

Can you make this function return int, do the error-handling, and return -1 on error and 0 on success? Unless it was intentional for some reason to leave the error handling different at all the call sites, I don't think it was.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually for NPY_DT_PyArray_ArrFuncs_fromstr NumPy passes its own endptr and we use exactly that in parsing, this way NumPy checks if *endptr moved forward (if not, it throws the "unmatched data" error). Here is the declaration link in NumPy link

That's why I thought to keep it like this.

Copy link
Member Author

Choose a reason for hiding this comment

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

So there are 2 cases, where we check the exceptions by defninig our own endptr and some where NumPy keeps track

Copy link
Member

Choose a reason for hiding this comment

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

Can't you handle that with a boolean flag or something? partial_conversion_check?

if (endptr == s) {
    // didn't parse anything
    return -1;
}
if (partial_conversion_check && endptr != "\0") {
    // characters remain to be converted
    return -1;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

It can be possible as

int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, 
char **endptr, bool require_full_parse)
{
  if(backend == BACKEND_SLEEF) {
    out_value->sleef_value = Sleef_strtoq(str, endptr);
  } else {
    out_value->longdouble_value = strtold(str, endptr);
  }
  if(*endptr == str) 
    return -1; // parse error - nothing was parsed
  
  // If full parse is required
  if(require_full_parse && **endptr != '\0')
    return -1; // parse error - characters remain to be converted
  
  return 0; // success
}

Both endptr and require_full_parse need to be coming from the calling context can decide passing true or false for example in NPY_DT_PyArray_ArrFuncs_fromstr should pass false and others true

Let me know @ngoldbaum if you want something like this then I can proceed.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think that's better than scattering the error checking throughout the code. Either way you need to know which kind of error checking you need to do.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

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

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

return 0; // success
}
11 changes: 11 additions & 0 deletions quaddtype/numpy_quaddtype/src/utilities.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef QUAD_UTILITIES_H
#define QUAD_UTILITIES_H

#include "quad_common.h"
#include <sleef.h>
#include <sleefquad.h>
#include <stdbool.h>

int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, char **endptr, bool require_full_parse);

#endif
Loading