Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 2 additions & 0 deletions quaddtype/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ srcs = [
'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: 0 additions & 1 deletion quaddtype/numpy_quaddtype/_quaddtype_main.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ _Backend: TypeAlias = Literal["sleef", "longdouble"]
_IntoQuad: TypeAlias = (
QuadPrecision
| float
| int
| str
| bytes
| np.floating[Any]
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;
cstring_to_quad(buffer, descr->backend, &val, &endptr);
if (endptr == buffer) {
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;
cstring_to_quad(s, descr->backend, &val, endptr);
if (*endptr == s) {
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
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
15 changes: 3 additions & 12 deletions quaddtype/numpy_quaddtype/src/scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#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)
Expand Down Expand Up @@ -196,12 +197,7 @@ 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);
}
else {
self->value.longdouble_value = strtold(s, &endptr);
}
cstring_to_quad(s, backend, &self->value, &endptr);
if (*endptr != '\0' || endptr == s) {
PyErr_SetString(PyExc_ValueError, "Unable to parse string to QuadPrecision");
Py_DECREF(self);
Expand All @@ -215,12 +211,7 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend)
return NULL;
}
char *endptr = NULL;
if (backend == BACKEND_SLEEF) {
self->value.sleef_value = Sleef_strtoq(s, &endptr);
}
else {
self->value.longdouble_value = strtold(s, &endptr);
}
cstring_to_quad(s, backend, &self->value, &endptr);
if (*endptr != '\0' || endptr == s) {
PyErr_SetString(PyExc_ValueError, "Unable to parse bytes to QuadPrecision");
Py_DECREF(self);
Expand Down
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
13 changes: 13 additions & 0 deletions quaddtype/numpy_quaddtype/src/utilities.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "utilities.h"

void cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, char **endptr)
{
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

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

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

void cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, char **endptr);

#endif
Loading