Skip to content

Commit 1e69bec

Browse files
committed
pickle from raw bytes and reconstruct
1 parent e328d11 commit 1e69bec

3 files changed

Lines changed: 98 additions & 42 deletions

File tree

src/csrc/quaddtype_main.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ get_sleef_constant(PyObject *self, PyObject *args)
6868
static PyMethodDef module_methods[] = {
6969
{"is_longdouble_128", py_is_longdouble_128, METH_NOARGS, "Check if long double is 128-bit"},
7070
{"get_sleef_constant", get_sleef_constant, METH_VARARGS, "Get Sleef constant by name"},
71+
{"from_raw_bytes", QuadPrecision_from_raw_bytes, METH_VARARGS,
72+
"Reconstruct a QuadPrecision scalar from its raw little-endian bytes "
73+
"(used by pickle)."},
7174
{"set_num_threads", py_quadblas_set_num_threads, METH_VARARGS,
7275
"Set number of threads for QuadBLAS"},
7376
{"get_num_threads", py_quadblas_get_num_threads, METH_NOARGS,

src/csrc/scalar.c

Lines changed: 95 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -620,68 +620,130 @@ QuadPrecision_as_integer_ratio(QuadPrecisionObject *self, PyObject *Py_UNUSED(ig
620620
return PyTuple_Pack(2, numerator, denominator);
621621
}
622622

623-
static PyObject *
624-
QuadPrecision_reduce(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored))
623+
static int
624+
quad_host_is_big_endian(void)
625625
{
626-
Dragon4_Options opt = {.scientific = 1,
627-
.digit_mode = DigitMode_Unique,
628-
.cutoff_mode = CutoffMode_TotalLength,
629-
.precision = SLEEF_QUAD_DECIMAL_DIG,
630-
.sign = 1,
631-
.trim_mode = TrimMode_LeaveOneZero,
632-
.digits_left = 1,
633-
.exp_digits = 4};
626+
uint16_t probe = 1;
627+
return ((const unsigned char *)&probe)[0] == 0;
628+
}
634629

635-
PyObject *str_value;
636-
if (self->backend == BACKEND_SLEEF) {
637-
str_value = Dragon4_Scientific_QuadDType(&self->value.sleef_value, opt.digit_mode,
638-
opt.precision, opt.min_digits, opt.sign,
639-
opt.trim_mode, opt.digits_left, opt.exp_digits);
630+
631+
static void
632+
quad_copy_canonical(unsigned char *dst, const unsigned char *src, size_t n)
633+
{
634+
if (quad_host_is_big_endian()) {
635+
for (size_t i = 0; i < n; i++) {
636+
dst[i] = src[n - 1 - i];
637+
}
640638
}
641639
else {
642-
char buffer[128];
643-
int written = snprintf(buffer, sizeof(buffer), "%.*Le",
644-
LDBL_DECIMAL_DIG - 1, self->value.longdouble_value);
645-
if (written < 0 || written >= (int)sizeof(buffer)) {
646-
PyErr_SetString(PyExc_RuntimeError,
647-
"Failed to format long double for pickle");
648-
return NULL;
649-
}
650-
str_value = PyUnicode_FromString(buffer);
640+
memcpy(dst, src, n);
651641
}
652-
if (str_value == NULL) {
642+
}
643+
644+
645+
static PyObject *
646+
QuadPrecision_reduce(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored))
647+
{
648+
size_t nbytes = (self->backend == BACKEND_SLEEF)
649+
? sizeof(self->value.sleef_value)
650+
: sizeof(self->value.longdouble_value);
651+
const unsigned char *src = (self->backend == BACKEND_SLEEF)
652+
? (const unsigned char *)&self->value.sleef_value
653+
: (const unsigned char *)&self->value.longdouble_value;
654+
655+
unsigned char raw[sizeof(quad_value)];
656+
quad_copy_canonical(raw, src, nbytes);
657+
658+
PyObject *data = PyBytes_FromStringAndSize((const char *)raw, (Py_ssize_t)nbytes);
659+
if (data == NULL) {
653660
return NULL;
654661
}
655662

656663
PyObject *backend_obj = PyUnicode_FromString(
657664
self->backend == BACKEND_SLEEF ? "sleef" : "longdouble");
658665
if (backend_obj == NULL) {
659-
Py_DECREF(str_value);
666+
Py_DECREF(data);
660667
return NULL;
661668
}
662669

663-
PyObject *args = PyTuple_Pack(2, str_value, backend_obj);
664-
Py_DECREF(str_value);
670+
PyObject *module = PyImport_ImportModule("numpy_quaddtype._quaddtype_main");
671+
if (module == NULL) {
672+
Py_DECREF(data);
673+
Py_DECREF(backend_obj);
674+
return NULL;
675+
}
676+
PyObject *reconstruct = PyObject_GetAttrString(module, "from_raw_bytes");
677+
Py_DECREF(module);
678+
if (reconstruct == NULL) {
679+
Py_DECREF(data);
680+
Py_DECREF(backend_obj);
681+
return NULL;
682+
}
683+
684+
PyObject *args = PyTuple_Pack(2, data, backend_obj);
685+
Py_DECREF(data);
665686
Py_DECREF(backend_obj);
666687
if (args == NULL) {
688+
Py_DECREF(reconstruct);
667689
return NULL;
668690
}
669691

670-
PyObject *type_obj = (PyObject *)Py_TYPE(self);
671-
Py_INCREF(type_obj);
672-
PyObject *result = PyTuple_Pack(2, type_obj, args);
673-
Py_DECREF(type_obj);
692+
PyObject *result = PyTuple_Pack(2, reconstruct, args);
693+
Py_DECREF(reconstruct);
674694
Py_DECREF(args);
675695
return result;
676696
}
677697

698+
PyObject *
699+
QuadPrecision_from_raw_bytes(PyObject *Py_UNUSED(module), PyObject *args)
700+
{
701+
Py_buffer view;
702+
const char *backend_str = "sleef";
703+
if (!PyArg_ParseTuple(args, "y*|s", &view, &backend_str)) {
704+
return NULL;
705+
}
706+
707+
QuadBackendType backend = BACKEND_SLEEF;
708+
if (strcmp(backend_str, "longdouble") == 0) {
709+
backend = BACKEND_LONGDOUBLE;
710+
}
711+
else if (strcmp(backend_str, "sleef") != 0) {
712+
PyBuffer_Release(&view);
713+
PyErr_SetString(PyExc_ValueError, "Invalid backend. Use 'sleef' or 'longdouble'.");
714+
return NULL;
715+
}
716+
717+
size_t expected = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double);
718+
if (view.len != (Py_ssize_t)expected) {
719+
PyErr_Format(PyExc_ValueError,
720+
"QuadPrecision.from_raw_bytes expected %zu bytes for the '%s' "
721+
"backend, got %zd",
722+
expected, backend_str, view.len);
723+
PyBuffer_Release(&view);
724+
return NULL;
725+
}
726+
727+
QuadPrecisionObject *self = QuadPrecision_raw_new(backend);
728+
if (self == NULL) {
729+
PyBuffer_Release(&view);
730+
return NULL;
731+
}
732+
unsigned char *dst = (backend == BACKEND_SLEEF)
733+
? (unsigned char *)&self->value.sleef_value
734+
: (unsigned char *)&self->value.longdouble_value;
735+
quad_copy_canonical(dst, (const unsigned char *)view.buf, expected);
736+
PyBuffer_Release(&view);
737+
return (PyObject *)self;
738+
}
739+
678740
static PyMethodDef QuadPrecision_methods[] = {
679741
{"is_integer", (PyCFunction)QuadPrecision_is_integer, METH_NOARGS,
680742
"Return True if the value is an integer."},
681743
{"as_integer_ratio", (PyCFunction)QuadPrecision_as_integer_ratio, METH_NOARGS,
682744
"Return a pair of integers whose ratio is exactly equal to the original value."},
683745
{"__reduce__", (PyCFunction)QuadPrecision_reduce, METH_NOARGS,
684-
"Support pickling: return (QuadPrecision, (str_value, backend))."},
746+
"Support pickling: return (from_raw_bytes, (raw_bytes, backend))."},
685747
{NULL, NULL, 0, NULL} /* Sentinel */
686748
};
687749

src/include/constants.hpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ extern "C" {
99
#include <sleefquad.h>
1010
#include <stdint.h>
1111
#include <string.h>
12-
#include <float.h>
13-
14-
/* LDBL_DECIMAL_DIG: minimum decimal digits needed for a lossless
15-
* long-double → string → long-double round-trip. Standard C11 constant in
16-
* <float.h>, but MSVC omits it. On MSVC long double is the same width as
17-
* double, so DBL_DECIMAL_DIG (17) is the exact correct fallback. */
18-
#ifndef LDBL_DECIMAL_DIG
19-
# define LDBL_DECIMAL_DIG DBL_DECIMAL_DIG
20-
#endif
2112

2213
// Quad precision constants using sleef_q macro
2314
#define QUAD_PRECISION_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383)

0 commit comments

Comments
 (0)