33#include <sleefquad.h>
44#include <stdlib.h>
55#include <math.h>
6+ #include <string.h>
7+ #include <float.h>
68
79#define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API
810#define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION
@@ -40,6 +42,8 @@ QuadPrecision_raw_new(QuadBackendType backend)
4042 new -> value .sleef_value = Sleef_cast_from_doubleq1 (0.0 );
4143 }
4244 else {
45+ // An 80-bit long double occupies 16 bytes but writes only 10
46+ memset (& new -> value , 0 , sizeof (new -> value ));
4347 new -> value .longdouble_value = 0.0L ;
4448 }
4549 return new ;
@@ -335,19 +339,6 @@ QuadPrecision_str_dragon4(QuadPrecisionObject *self)
335339 }
336340}
337341
338- static PyObject *
339- QuadPrecision_str (QuadPrecisionObject * self )
340- {
341- char buffer [128 ];
342- if (self -> backend == BACKEND_SLEEF ) {
343- Sleef_snprintf (buffer , sizeof (buffer ), "%.*Qe" , SLEEF_QUAD_DIG , self -> value .sleef_value );
344- }
345- else {
346- snprintf (buffer , sizeof (buffer ), "%.35Le" , self -> value .longdouble_value );
347- }
348- return PyUnicode_FromString (buffer );
349- }
350-
351342static PyObject *
352343QuadPrecision_repr_dragon4 (QuadPrecisionObject * self )
353344{
@@ -358,7 +349,7 @@ QuadPrecision_repr_dragon4(QuadPrecisionObject *self)
358349 .sign = 1 ,
359350 .trim_mode = TrimMode_LeaveOneZero ,
360351 .digits_left = 1 ,
361- .exp_digits = 3 };
352+ .exp_digits = 4 };
362353
363354 PyObject * str ;
364355 if (self -> backend == BACKEND_SLEEF ) {
@@ -633,11 +624,157 @@ QuadPrecision_as_integer_ratio(QuadPrecisionObject *self, PyObject *Py_UNUSED(ig
633624 return PyTuple_Pack (2 , numerator , denominator );
634625}
635626
627+ static int
628+ quad_host_is_big_endian (void )
629+ {
630+ uint16_t probe = 1 ;
631+ return ((const unsigned char * )& probe )[0 ] == 0 ;
632+ }
633+
634+
635+ static void
636+ quad_copy_canonical (unsigned char * dst , const unsigned char * src , size_t n )
637+ {
638+ if (quad_host_is_big_endian ()) {
639+ for (size_t i = 0 ; i < n ; i ++ ) {
640+ dst [i ] = src [n - 1 - i ];
641+ }
642+ }
643+ else {
644+ memcpy (dst , src , n );
645+ }
646+ }
647+
648+
649+ static PyObject *
650+ QuadPrecision_reduce (QuadPrecisionObject * self , PyObject * Py_UNUSED (ignored ))
651+ {
652+ size_t nbytes = (self -> backend == BACKEND_SLEEF )
653+ ? sizeof (self -> value .sleef_value )
654+ : sizeof (self -> value .longdouble_value );
655+ const unsigned char * src = (self -> backend == BACKEND_SLEEF )
656+ ? (const unsigned char * )& self -> value .sleef_value
657+ : (const unsigned char * )& self -> value .longdouble_value ;
658+
659+ unsigned char raw [sizeof (quad_value )];
660+ quad_copy_canonical (raw , src , nbytes );
661+
662+ PyObject * data = PyBytes_FromStringAndSize ((const char * )raw , (Py_ssize_t )nbytes );
663+ if (data == NULL ) {
664+ return NULL ;
665+ }
666+
667+ PyObject * backend_obj = PyUnicode_FromString (
668+ self -> backend == BACKEND_SLEEF ? "sleef" : "longdouble" );
669+ if (backend_obj == NULL ) {
670+ Py_DECREF (data );
671+ return NULL ;
672+ }
673+
674+ PyObject * module = PyImport_ImportModule ("numpy_quaddtype._quaddtype_main" );
675+ if (module == NULL ) {
676+ Py_DECREF (data );
677+ Py_DECREF (backend_obj );
678+ return NULL ;
679+ }
680+ PyObject * reconstruct = PyObject_GetAttrString (module , "from_raw_bytes" );
681+ Py_DECREF (module );
682+ if (reconstruct == NULL ) {
683+ Py_DECREF (data );
684+ Py_DECREF (backend_obj );
685+ return NULL ;
686+ }
687+
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+ }
704+ Py_DECREF (data );
705+ Py_DECREF (backend_obj );
706+ if (args == NULL ) {
707+ Py_DECREF (reconstruct );
708+ return NULL ;
709+ }
710+
711+ PyObject * result = PyTuple_Pack (2 , reconstruct , args );
712+ Py_DECREF (reconstruct );
713+ Py_DECREF (args );
714+ return result ;
715+ }
716+
717+ PyObject *
718+ QuadPrecision_from_raw_bytes (PyObject * Py_UNUSED (module ), PyObject * args )
719+ {
720+ Py_buffer view ;
721+ const char * backend_str = "sleef" ;
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 )) {
724+ return NULL ;
725+ }
726+
727+ QuadBackendType backend = BACKEND_SLEEF ;
728+ if (strcmp (backend_str , "longdouble" ) == 0 ) {
729+ backend = BACKEND_LONGDOUBLE ;
730+ }
731+ else if (strcmp (backend_str , "sleef" ) != 0 ) {
732+ PyBuffer_Release (& view );
733+ PyErr_SetString (PyExc_ValueError , "Invalid backend. Use 'sleef' or 'longdouble'." );
734+ return NULL ;
735+ }
736+
737+ size_t expected = (backend == BACKEND_SLEEF ) ? sizeof (Sleef_quad ) : sizeof (long double );
738+ if (view .len != (Py_ssize_t )expected ) {
739+ PyErr_Format (PyExc_ValueError ,
740+ "from_raw_bytes expected %zu bytes for the '%s' "
741+ "backend, got %zd" ,
742+ expected , backend_str , view .len );
743+ PyBuffer_Release (& view );
744+ return NULL ;
745+ }
746+
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+
758+ QuadPrecisionObject * self = QuadPrecision_raw_new (backend );
759+ if (self == NULL ) {
760+ PyBuffer_Release (& view );
761+ return NULL ;
762+ }
763+ unsigned char * dst = (backend == BACKEND_SLEEF )
764+ ? (unsigned char * )& self -> value .sleef_value
765+ : (unsigned char * )& self -> value .longdouble_value ;
766+ quad_copy_canonical (dst , (const unsigned char * )view .buf , expected );
767+ PyBuffer_Release (& view );
768+ return (PyObject * )self ;
769+ }
770+
636771static PyMethodDef QuadPrecision_methods [] = {
637772 {"is_integer" , (PyCFunction )QuadPrecision_is_integer , METH_NOARGS ,
638773 "Return True if the value is an integer." },
639774 {"as_integer_ratio" , (PyCFunction )QuadPrecision_as_integer_ratio , METH_NOARGS ,
640775 "Return a pair of integers whose ratio is exactly equal to the original value." },
776+ {"__reduce__" , (PyCFunction )QuadPrecision_reduce , METH_NOARGS ,
777+ "Support pickling: return (from_raw_bytes, (raw_bytes, backend[, ld_format]))." },
641778 {NULL , NULL , 0 , NULL } /* Sentinel */
642779};
643780
0 commit comments