@@ -14,6 +14,7 @@ extern "C" {
1414#include " numpy/dtype_api.h"
1515}
1616#include < cstring>
17+ #include < cstdlib>
1718#include " sleef.h"
1819#include " sleefquad.h"
1920
@@ -26,7 +27,7 @@ extern "C" {
2627#include " dragon4.h"
2728#include " ops.hpp"
2829
29- #define NUM_CASTS 36 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
30+ #define NUM_CASTS 38 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
3031#define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer
3132
3233static NPY_CASTING
@@ -525,6 +526,290 @@ quad_to_unicode_loop_aligned(PyArrayMethod_Context *context, char *const data[],
525526 return 0 ;
526527}
527528
529+ // Bytes to QuadDType casting
530+ static NPY_CASTING
531+ bytes_to_quad_resolve_descriptors (PyObject *NPY_UNUSED (self), PyArray_DTypeMeta *dtypes[2],
532+ PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
533+ npy_intp *view_offset)
534+ {
535+ // Bytes dtype doesn't have byte order concerns like Unicode
536+ Py_INCREF (given_descrs[0 ]);
537+ loop_descrs[0 ] = given_descrs[0 ];
538+
539+ if (given_descrs[1 ] == NULL ) {
540+ loop_descrs[1 ] = (PyArray_Descr *)new_quaddtype_instance (BACKEND_SLEEF);
541+ if (loop_descrs[1 ] == nullptr ) {
542+ Py_DECREF (loop_descrs[0 ]);
543+ return (NPY_CASTING)-1 ;
544+ }
545+ }
546+ else {
547+ Py_INCREF (given_descrs[1 ]);
548+ loop_descrs[1 ] = given_descrs[1 ];
549+ }
550+
551+ return NPY_UNSAFE_CASTING;
552+ }
553+
554+ // Helper function: Convert bytes string to quad_value
555+ static inline int
556+ bytes_to_quad_convert (const char *bytes_str, npy_intp bytes_size,
557+ QuadBackendType backend, quad_value *out_val)
558+ {
559+ // Create a null-terminated copy since bytes might not be null-terminated
560+ char *temp_str = (char *)malloc (bytes_size + 1 );
561+ if (temp_str == NULL ) {
562+ PyErr_NoMemory ();
563+ return -1 ;
564+ }
565+
566+ memcpy (temp_str, bytes_str, bytes_size);
567+ temp_str[bytes_size] = ' \0 ' ;
568+
569+ // Find the actual end (null byte or first occurrence)
570+ npy_intp actual_len = 0 ;
571+ while (actual_len < bytes_size && temp_str[actual_len] != ' \0 ' ) {
572+ actual_len++;
573+ }
574+ temp_str[actual_len] = ' \0 ' ;
575+
576+ char *endptr;
577+ int err = NumPyOS_ascii_strtoq (temp_str, backend, out_val, &endptr);
578+
579+ if (err < 0 ) {
580+ PyErr_Format (PyExc_ValueError,
581+ " could not convert bytes to QuadPrecision: np.bytes_(%s)" , temp_str);
582+ free (temp_str);
583+ return -1 ;
584+ }
585+
586+ while (ascii_isspace (*endptr)) {
587+ endptr++;
588+ }
589+
590+ if (*endptr != ' \0 ' ) {
591+ PyErr_Format (PyExc_ValueError,
592+ " could not convert bytes to QuadPrecision: np.bytes_(%s)" , temp_str);
593+ free (temp_str);
594+ return -1 ;
595+ }
596+
597+ free (temp_str);
598+ return 0 ;
599+ }
600+
601+ static int
602+ bytes_to_quad_strided_loop_unaligned (PyArrayMethod_Context *context, char *const data[],
603+ npy_intp const dimensions[], npy_intp const strides[],
604+ void *NPY_UNUSED (auxdata))
605+ {
606+ npy_intp N = dimensions[0 ];
607+ char *in_ptr = data[0 ];
608+ char *out_ptr = data[1 ];
609+ npy_intp in_stride = strides[0 ];
610+ npy_intp out_stride = strides[1 ];
611+
612+ PyArray_Descr *const *descrs = context->descriptors ;
613+ QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1 ];
614+ QuadBackendType backend = descr_out->backend ;
615+
616+ npy_intp bytes_size = descrs[0 ]->elsize ;
617+ size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof (Sleef_quad) : sizeof (long double );
618+
619+ while (N--) {
620+ quad_value out_val;
621+
622+ if (bytes_to_quad_convert (in_ptr, bytes_size, backend, &out_val) < 0 ) {
623+ return -1 ;
624+ }
625+
626+ memcpy (out_ptr, &out_val, elem_size);
627+
628+ in_ptr += in_stride;
629+ out_ptr += out_stride;
630+ }
631+
632+ return 0 ;
633+ }
634+
635+ static int
636+ bytes_to_quad_strided_loop_aligned (PyArrayMethod_Context *context, char *const data[],
637+ npy_intp const dimensions[], npy_intp const strides[],
638+ void *NPY_UNUSED (auxdata))
639+ {
640+ npy_intp N = dimensions[0 ];
641+ char *in_ptr = data[0 ];
642+ char *out_ptr = data[1 ];
643+ npy_intp in_stride = strides[0 ];
644+ npy_intp out_stride = strides[1 ];
645+
646+ PyArray_Descr *const *descrs = context->descriptors ;
647+ QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1 ];
648+ QuadBackendType backend = descr_out->backend ;
649+
650+ npy_intp bytes_size = descrs[0 ]->elsize ;
651+
652+ while (N--) {
653+ quad_value out_val;
654+
655+ if (bytes_to_quad_convert (in_ptr, bytes_size, backend, &out_val) < 0 ) {
656+ return -1 ;
657+ }
658+
659+ if (backend == BACKEND_SLEEF) {
660+ *(Sleef_quad *)(out_ptr) = out_val.sleef_value ;
661+ }
662+ else {
663+ *(long double *)(out_ptr) = out_val.longdouble_value ;
664+ }
665+
666+ in_ptr += in_stride;
667+ out_ptr += out_stride;
668+ }
669+
670+ return 0 ;
671+ }
672+
673+ // QuadDType to bytes
674+ static NPY_CASTING
675+ quad_to_bytes_resolve_descriptors (PyObject *NPY_UNUSED (self), PyArray_DTypeMeta *dtypes[2],
676+ PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
677+ npy_intp *view_offset)
678+ {
679+ npy_intp required_size_bytes = QUAD_STR_WIDTH;
680+
681+ if (given_descrs[1 ] == NULL ) {
682+ PyArray_Descr *new_descr = PyArray_DescrNewFromType (NPY_STRING);
683+ if (new_descr == NULL ) {
684+ return (NPY_CASTING)-1 ;
685+ }
686+ new_descr->elsize = required_size_bytes;
687+ loop_descrs[1 ] = new_descr;
688+ }
689+ else {
690+ Py_INCREF (given_descrs[1 ]);
691+ loop_descrs[1 ] = given_descrs[1 ];
692+ }
693+
694+ Py_INCREF (given_descrs[0 ]);
695+ loop_descrs[0 ] = given_descrs[0 ];
696+
697+ *view_offset = 0 ;
698+
699+ // If target descriptor is wide enough, it's a safe cast
700+ if (loop_descrs[1 ]->elsize >= required_size_bytes) {
701+ return NPY_SAFE_CASTING;
702+ }
703+ return NPY_SAME_KIND_CASTING;
704+ }
705+
706+ // Helper function: Copy string to bytes output buffer
707+ static inline void
708+ copy_string_to_bytes (const char *str, char *out_bytes, npy_intp bytes_size)
709+ {
710+ npy_intp str_len = strlen (str);
711+
712+ npy_intp copy_len = (str_len < bytes_size) ? str_len : bytes_size;
713+ memcpy (out_bytes, str, copy_len);
714+
715+ // Pad remaining space with null bytes
716+ for (npy_intp i = copy_len; i < bytes_size; i++) {
717+ out_bytes[i] = ' \0 ' ;
718+ }
719+ }
720+
721+ static int
722+ quad_to_bytes_loop_unaligned (PyArrayMethod_Context *context, char *const data[],
723+ npy_intp const dimensions[], npy_intp const strides[],
724+ void *NPY_UNUSED (auxdata))
725+ {
726+ npy_intp N = dimensions[0 ];
727+ char *in_ptr = data[0 ];
728+ char *out_ptr = data[1 ];
729+ npy_intp in_stride = strides[0 ];
730+ npy_intp out_stride = strides[1 ];
731+
732+ PyArray_Descr *const *descrs = context->descriptors ;
733+ QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0 ];
734+ QuadBackendType backend = descr_in->backend ;
735+
736+ npy_intp bytes_size = descrs[1 ]->elsize ;
737+ size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof (Sleef_quad) : sizeof (long double );
738+
739+ while (N--) {
740+ quad_value in_val;
741+ if (backend == BACKEND_SLEEF) {
742+ memcpy (&in_val.sleef_value , in_ptr, sizeof (Sleef_quad));
743+ }
744+ else {
745+ memcpy (&in_val.longdouble_value , in_ptr, sizeof (long double ));
746+ }
747+ Sleef_quad sleef_val = quad_to_sleef_quad (&in_val, backend);
748+ PyObject *py_str = quad_to_string_adaptive (&sleef_val, bytes_size);
749+ if (py_str == NULL ) {
750+ return -1 ;
751+ }
752+ const char *temp_str = PyUnicode_AsUTF8 (py_str);
753+ if (temp_str == NULL ) {
754+ Py_DECREF (py_str);
755+ return -1 ;
756+ }
757+
758+ copy_string_to_bytes (temp_str, out_ptr, bytes_size);
759+
760+ Py_DECREF (py_str);
761+
762+ in_ptr += in_stride;
763+ out_ptr += out_stride;
764+ }
765+
766+ return 0 ;
767+ }
768+
769+ static int
770+ quad_to_bytes_loop_aligned (PyArrayMethod_Context *context, char *const data[],
771+ npy_intp const dimensions[], npy_intp const strides[],
772+ void *NPY_UNUSED (auxdata))
773+ {
774+ npy_intp N = dimensions[0 ];
775+ char *in_ptr = data[0 ];
776+ char *out_ptr = data[1 ];
777+ npy_intp in_stride = strides[0 ];
778+ npy_intp out_stride = strides[1 ];
779+
780+ PyArray_Descr *const *descrs = context->descriptors ;
781+ QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0 ];
782+ QuadBackendType backend = descr_in->backend ;
783+
784+ npy_intp bytes_size = descrs[1 ]->elsize ;
785+
786+ while (N--) {
787+ quad_value in_val;
788+ if (backend == BACKEND_SLEEF) {
789+ in_val.sleef_value = *(Sleef_quad *)in_ptr;
790+ }
791+ else {
792+ in_val.longdouble_value = *(long double *)in_ptr;
793+ }
794+ Sleef_quad sleef_val = quad_to_sleef_quad (&in_val, backend);
795+ PyObject *py_str = quad_to_string_adaptive (&sleef_val, bytes_size);
796+ if (py_str == NULL ) {
797+ return -1 ;
798+ }
799+ const char *temp_str = PyUnicode_AsUTF8 (py_str);
800+ if (temp_str == NULL ) {
801+ Py_DECREF (py_str);
802+ return -1 ;
803+ }
804+
805+ copy_string_to_bytes (temp_str, out_ptr, bytes_size); Py_DECREF (py_str);
806+ in_ptr += in_stride;
807+ out_ptr += out_stride;
808+ }
809+
810+ return 0 ;
811+ }
812+
528813// Tag dispatching to ensure npy_bool/npy_ubyte and npy_half/npy_ushort do not alias in templates
529814// see e.g. https://stackoverflow.com/q/32522279
530815struct spec_npy_bool {};
@@ -1277,6 +1562,44 @@ init_casts_internal(void)
12771562 };
12781563 add_spec (quad_to_unicode_spec);
12791564
1565+ // Bytes to QuadPrecision cast
1566+ PyArray_DTypeMeta **bytes_to_quad_dtypes = new PyArray_DTypeMeta *[2 ]{&PyArray_BytesDType, &QuadPrecDType};
1567+ PyType_Slot *bytes_to_quad_slots = new PyType_Slot[4 ]{
1568+ {NPY_METH_resolve_descriptors, (void *)&bytes_to_quad_resolve_descriptors},
1569+ {NPY_METH_strided_loop, (void *)&bytes_to_quad_strided_loop_aligned},
1570+ {NPY_METH_unaligned_strided_loop, (void *)&bytes_to_quad_strided_loop_unaligned},
1571+ {0 , nullptr }};
1572+
1573+ PyArrayMethod_Spec *bytes_to_quad_spec = new PyArrayMethod_Spec{
1574+ .name = " cast_Bytes_to_QuadPrec" ,
1575+ .nin = 1 ,
1576+ .nout = 1 ,
1577+ .casting = NPY_UNSAFE_CASTING,
1578+ .flags = NPY_METH_SUPPORTS_UNALIGNED,
1579+ .dtypes = bytes_to_quad_dtypes,
1580+ .slots = bytes_to_quad_slots,
1581+ };
1582+ add_spec (bytes_to_quad_spec);
1583+
1584+ // QuadPrecision to Bytes
1585+ PyArray_DTypeMeta **quad_to_bytes_dtypes = new PyArray_DTypeMeta *[2 ]{&QuadPrecDType, &PyArray_BytesDType};
1586+ PyType_Slot *quad_to_bytes_slots = new PyType_Slot[4 ]{
1587+ {NPY_METH_resolve_descriptors, (void *)&quad_to_bytes_resolve_descriptors},
1588+ {NPY_METH_strided_loop, (void *)&quad_to_bytes_loop_aligned},
1589+ {NPY_METH_unaligned_strided_loop, (void *)&quad_to_bytes_loop_unaligned},
1590+ {0 , nullptr }};
1591+
1592+ PyArrayMethod_Spec *quad_to_bytes_spec = new PyArrayMethod_Spec{
1593+ .name = " cast_QuadPrec_to_Bytes" ,
1594+ .nin = 1 ,
1595+ .nout = 1 ,
1596+ .casting = NPY_UNSAFE_CASTING,
1597+ .flags = NPY_METH_SUPPORTS_UNALIGNED,
1598+ .dtypes = quad_to_bytes_dtypes,
1599+ .slots = quad_to_bytes_slots,
1600+ };
1601+ add_spec (quad_to_bytes_spec);
1602+
12801603 specs[spec_count] = nullptr ;
12811604 return specs;
12821605}
0 commit comments