Skip to content

Commit ee40e6d

Browse files
committed
tag ldbl and error in incompat platform
1 parent 7574ada commit ee40e6d

3 files changed

Lines changed: 58 additions & 8 deletions

File tree

src/csrc/quaddtype_main.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,10 @@ 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"},
7171
{"from_raw_bytes", QuadPrecision_from_raw_bytes, METH_VARARGS,
72-
"Reconstruct a QuadPrecision scalar from its raw little-endian bytes "
73-
"(used by pickle)."},
72+
"from_raw_bytes(data, backend='sleef', ld_format=-1): reconstruct a "
73+
"QuadPrecision scalar from its raw little-endian bytes (used by pickle). "
74+
"For the 'longdouble' backend ld_format is the source LDBL_MANT_DIG and, "
75+
"if given, must match this platform's."},
7476
{"set_num_threads", py_quadblas_set_num_threads, METH_VARARGS,
7577
"Set number of threads for QuadBLAS"},
7678
{"get_num_threads", py_quadblas_get_num_threads, METH_NOARGS,

src/csrc/scalar.c

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <stdlib.h>
55
#include <math.h>
66
#include <string.h>
7+
#include <float.h>
78

89
#define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API
910
#define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION
@@ -684,7 +685,22 @@ QuadPrecision_reduce(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored))
684685
return NULL;
685686
}
686687

687-
PyObject *args = PyTuple_Pack(2, data, backend_obj);
688+
PyObject *args;
689+
if (self->backend == BACKEND_LONGDOUBLE) {
690+
// Tag longdouble payloads with the platform's LDBL_MANT_DIG
691+
PyObject *fmt = PyLong_FromLong((long)LDBL_MANT_DIG);
692+
if (fmt == NULL) {
693+
Py_DECREF(data);
694+
Py_DECREF(backend_obj);
695+
Py_DECREF(reconstruct);
696+
return NULL;
697+
}
698+
args = PyTuple_Pack(3, data, backend_obj, fmt);
699+
Py_DECREF(fmt);
700+
}
701+
else {
702+
args = PyTuple_Pack(2, data, backend_obj);
703+
}
688704
Py_DECREF(data);
689705
Py_DECREF(backend_obj);
690706
if (args == NULL) {
@@ -703,7 +719,8 @@ QuadPrecision_from_raw_bytes(PyObject *Py_UNUSED(module), PyObject *args)
703719
{
704720
Py_buffer view;
705721
const char *backend_str = "sleef";
706-
if (!PyArg_ParseTuple(args, "y*|s", &view, &backend_str)) {
722+
int ld_format = -1; // source LDBL_MANT_DIG for longdouble; -1 = not provided
723+
if (!PyArg_ParseTuple(args, "y*|si", &view, &backend_str, &ld_format)) {
707724
return NULL;
708725
}
709726

@@ -727,6 +744,17 @@ QuadPrecision_from_raw_bytes(PyObject *Py_UNUSED(module), PyObject *args)
727744
return NULL;
728745
}
729746

747+
if (backend == BACKEND_LONGDOUBLE && ld_format != -1 &&
748+
ld_format != LDBL_MANT_DIG) {
749+
PyErr_Format(PyExc_ValueError,
750+
"Cannot unpickle a 'longdouble' QuadPrecision created on a "
751+
"platform with a different long double format "
752+
"(LDBL_MANT_DIG=%d) than this one (LDBL_MANT_DIG=%d).",
753+
ld_format, (int)LDBL_MANT_DIG);
754+
PyBuffer_Release(&view);
755+
return NULL;
756+
}
757+
730758
QuadPrecisionObject *self = QuadPrecision_raw_new(backend);
731759
if (self == NULL) {
732760
PyBuffer_Release(&view);
@@ -746,7 +774,7 @@ static PyMethodDef QuadPrecision_methods[] = {
746774
{"as_integer_ratio", (PyCFunction)QuadPrecision_as_integer_ratio, METH_NOARGS,
747775
"Return a pair of integers whose ratio is exactly equal to the original value."},
748776
{"__reduce__", (PyCFunction)QuadPrecision_reduce, METH_NOARGS,
749-
"Support pickling: return (from_raw_bytes, (raw_bytes, backend))."},
777+
"Support pickling: return (from_raw_bytes, (raw_bytes, backend[, ld_format]))."},
750778
{NULL, NULL, 0, NULL} /* Sentinel */
751779
};
752780

tests/test_quaddtype.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5739,9 +5739,9 @@ def test_from_raw_bytes_roundtrip_matches_reduce(self):
57395739
for backend in ("sleef", "longdouble"):
57405740
original = QuadPrecision("3.14159265358979323846264338327950288",
57415741
backend=backend)
5742-
_, (data, be) = original.__reduce__()
5743-
assert be == backend
5744-
rebuilt = from_raw_bytes(data, backend)
5742+
_, reduce_args = original.__reduce__()
5743+
assert reduce_args[1] == backend
5744+
rebuilt = from_raw_bytes(*reduce_args)
57455745
assert self._raw_bytes(rebuilt) == self._raw_bytes(original)
57465746
assert rebuilt == original
57475747

@@ -5772,6 +5772,26 @@ def test_pickle_scalar_longdouble_padding_zeroed(self):
57725772
assert raw2[10:] == b"\x00" * 6, raw2.hex()
57735773
assert raw2 == raw
57745774

5775+
def test_reduce_longdouble_carries_format_tag(self):
5776+
"""longdouble __reduce__ carries a format tag (LDBL_MANT_DIG) so a
5777+
cross-format pickle can be rejected; sleef (always binary128) omits it."""
5778+
_, sl_args = QuadPrecision("1.5", backend="sleef").__reduce__()
5779+
_, ld_args = QuadPrecision("1.5", backend="longdouble").__reduce__()
5780+
assert len(sl_args) == 2 and sl_args[1] == "sleef"
5781+
assert len(ld_args) == 3 and ld_args[1] == "longdouble"
5782+
assert isinstance(ld_args[2], int)
5783+
5784+
def test_from_raw_bytes_rejects_wrong_longdouble_format(self):
5785+
"""A longdouble payload tagged with a different platform's format must be
5786+
rejected, not silently reinterpreted."""
5787+
from numpy_quaddtype._quaddtype_main import from_raw_bytes
5788+
_, (data, be, fmt) = QuadPrecision("1.5", backend="longdouble").__reduce__()
5789+
with pytest.raises(ValueError):
5790+
from_raw_bytes(data, "longdouble", fmt + 1)
5791+
# The matching tag (and omitting it) still reconstruct fine.
5792+
assert from_raw_bytes(data, "longdouble", fmt) == QuadPrecision("1.5", backend="longdouble")
5793+
assert from_raw_bytes(data, "longdouble") == QuadPrecision("1.5", backend="longdouble")
5794+
57755795

57765796
@pytest.mark.parametrize("dtype", [
57775797
"bool",

0 commit comments

Comments
 (0)