@@ -27,7 +27,7 @@ extern "C" {
2727#include " dragon4.h"
2828#include " ops.hpp"
2929
30- #define NUM_CASTS 38 // 17 to_casts + 17 from_casts + 1 quad_to_quad + 1 void_to_quad
30+ #define NUM_CASTS 40 // 18 to_casts + 18 from_casts + 1 quad_to_quad + 1 void_to_quad
3131#define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer
3232
3333static NPY_CASTING
@@ -360,13 +360,44 @@ quad_to_string_adaptive(Sleef_quad *sleef_val, npy_intp unicode_size_chars)
360360 if (pos_len <= unicode_size_chars) {
361361 return positional_str; // Keep the positional string
362362 }
363- else {
364- Py_DECREF (positional_str);
365- // Use scientific notation with full precision
366- return Dragon4_Scientific_QuadDType (sleef_val, DigitMode_Unique,
367- SLEEF_QUAD_DECIMAL_DIG, 0 , 1 ,
368- TrimMode_LeaveOneZero, 1 , 2 );
363+ Py_DECREF (positional_str);
364+ // Use scientific notation with full precision
365+ return Dragon4_Scientific_QuadDType (sleef_val, DigitMode_Unique,
366+ SLEEF_QUAD_DECIMAL_DIG, 0 , 1 ,
367+ TrimMode_LeaveOneZero, 1 , 2 );
368+ }
369+
370+ static inline const char *
371+ quad_to_string_adaptive_cstr (Sleef_quad *sleef_val, npy_intp unicode_size_chars)
372+ {
373+ // Try positional format first to see if it would fit
374+ const char * positional_str = Dragon4_Positional_QuadDType_CStr (
375+ sleef_val, DigitMode_Unique, CutoffMode_TotalLength, SLEEF_QUAD_DECIMAL_DIG, 0 , 1 ,
376+ TrimMode_LeaveOneZero, 1 , 0 );
377+
378+ if (positional_str == NULL ) {
379+ PyErr_SetString (PyExc_RuntimeError, " Float formatting failed" );
380+ return NULL ;
381+ }
382+
383+ // no need to scan full, only checking if its longer
384+ npy_intp pos_len = strnlen (positional_str, unicode_size_chars + 1 );
385+
386+ // If positional format fits, use it; otherwise use scientific notation
387+ if (pos_len <= unicode_size_chars) {
388+ return positional_str; // Keep the positional string
369389 }
390+
391+ // Use scientific notation with full precision
392+ const char *scientific_str = Dragon4_Scientific_QuadDType_CStr (sleef_val, DigitMode_Unique,
393+ SLEEF_QUAD_DECIMAL_DIG, 0 , 1 ,
394+ TrimMode_LeaveOneZero, 1 , 2 );
395+ if (scientific_str == NULL ) {
396+ PyErr_SetString (PyExc_RuntimeError, " Float formatting failed" );
397+ return NULL ;
398+ }
399+ return scientific_str;
400+
370401}
371402
372403template <bool Aligned>
@@ -605,6 +636,163 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[],
605636 return 0 ;
606637}
607638
639+ // StringDType to QuadDType casting
640+ static NPY_CASTING
641+ stringdtype_to_quad_resolve_descriptors (PyObject *NPY_UNUSED (self), PyArray_DTypeMeta *dtypes[2],
642+ PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
643+ npy_intp *view_offset)
644+ {
645+ if (given_descrs[1 ] == NULL ) {
646+ loop_descrs[1 ] = (PyArray_Descr *)new_quaddtype_instance (BACKEND_SLEEF);
647+ if (loop_descrs[1 ] == nullptr ) {
648+ return (NPY_CASTING)-1 ;
649+ }
650+ }
651+ else {
652+ Py_INCREF (given_descrs[1 ]);
653+ loop_descrs[1 ] = given_descrs[1 ];
654+ }
655+
656+ Py_INCREF (given_descrs[0 ]);
657+ loop_descrs[0 ] = given_descrs[0 ];
658+
659+ return NPY_UNSAFE_CASTING;
660+ }
661+
662+ // Note: StringDType elements are always aligned, so Aligned template parameter
663+ // is kept for API consistency but both versions use the same logic
664+ template <bool Aligned>
665+ static int
666+ stringdtype_to_quad_strided_loop (PyArrayMethod_Context *context, char *const data[],
667+ npy_intp const dimensions[], npy_intp const strides[],
668+ void *NPY_UNUSED (auxdata))
669+ {
670+ npy_intp N = dimensions[0 ];
671+ char *in_ptr = data[0 ];
672+ char *out_ptr = data[1 ];
673+ npy_intp in_stride = strides[0 ];
674+ npy_intp out_stride = strides[1 ];
675+
676+ PyArray_Descr *const *descrs = context->descriptors ;
677+ PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[0 ];
678+ QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)descrs[1 ];
679+ QuadBackendType backend = descr_out->backend ;
680+
681+ npy_string_allocator *allocator = NpyString_acquire_allocator (str_descr);
682+
683+ while (N--) {
684+ const npy_packed_static_string *ps = (npy_packed_static_string *)in_ptr;
685+ npy_static_string s = {0 , NULL };
686+ int is_null = NpyString_load (allocator, ps, &s);
687+
688+ if (is_null == -1 ) {
689+ NpyString_release_allocator (allocator);
690+ PyErr_SetString (PyExc_MemoryError, " Failed to load string in StringDType to Quad cast" );
691+ return -1 ;
692+ }
693+ else if (is_null) {
694+ // Handle null string - use the default string if available, otherwise error
695+ if (str_descr->has_string_na || str_descr->default_string .buf != NULL ) {
696+ s = str_descr->default_string ;
697+ }
698+ else {
699+ NpyString_release_allocator (allocator);
700+ PyErr_SetString (PyExc_ValueError, " Cannot convert null string to QuadPrecision" );
701+ return -1 ;
702+ }
703+ }
704+
705+ quad_value out_val;
706+ if (bytes_to_quad_convert (s.buf , s.size , backend, &out_val) < 0 ) {
707+ NpyString_release_allocator (allocator);
708+ return -1 ;
709+ }
710+
711+ store_quad<Aligned>(out_ptr, out_val, backend);
712+
713+ in_ptr += in_stride;
714+ out_ptr += out_stride;
715+ }
716+
717+ NpyString_release_allocator (allocator);
718+ return 0 ;
719+ }
720+
721+ // QuadDType to StringDType casting
722+ static NPY_CASTING
723+ quad_to_stringdtype_resolve_descriptors (PyObject *NPY_UNUSED (self), PyArray_DTypeMeta *dtypes[2],
724+ PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2],
725+ npy_intp *view_offset)
726+ {
727+ if (given_descrs[1 ] == NULL ) {
728+ // Default StringDType() already has coerce=True
729+ loop_descrs[1 ] = (PyArray_Descr *)PyObject_CallNoArgs (
730+ (PyObject *)&PyArray_StringDType);
731+ if (loop_descrs[1 ] == NULL ) {
732+ return (NPY_CASTING)-1 ;
733+ }
734+ }
735+ else {
736+ Py_INCREF (given_descrs[1 ]);
737+ loop_descrs[1 ] = given_descrs[1 ];
738+ }
739+
740+ Py_INCREF (given_descrs[0 ]);
741+ loop_descrs[0 ] = given_descrs[0 ];
742+
743+ return NPY_SAFE_CASTING;
744+ }
745+
746+ // Note: StringDType elements are always aligned, so Aligned template parameter
747+ // is kept for API consistency but both versions use the same logic
748+ template <bool Aligned>
749+ static int
750+ quad_to_stringdtype_strided_loop (PyArrayMethod_Context *context, char *const data[],
751+ npy_intp const dimensions[], npy_intp const strides[],
752+ void *NPY_UNUSED (auxdata))
753+ {
754+ npy_intp N = dimensions[0 ];
755+ char *in_ptr = data[0 ];
756+ char *out_ptr = data[1 ];
757+ npy_intp in_stride = strides[0 ];
758+ npy_intp out_stride = strides[1 ];
759+
760+ PyArray_Descr *const *descrs = context->descriptors ;
761+ QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0 ];
762+ PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[1 ];
763+ QuadBackendType backend = descr_in->backend ;
764+
765+ npy_string_allocator *allocator = NpyString_acquire_allocator (str_descr);
766+
767+ while (N--) {
768+ quad_value in_val = load_quad<Aligned>(in_ptr, backend);
769+ Sleef_quad sleef_val = quad_to_sleef_quad (&in_val, backend);
770+
771+ // Get string representation with adaptive notation
772+ // Use a large buffer size to allow for full precision
773+ const char *str_buf = quad_to_string_adaptive_cstr (&sleef_val, QUAD_STR_WIDTH);
774+ if (str_buf == NULL ) {
775+ NpyString_release_allocator (allocator);
776+ return -1 ;
777+ }
778+
779+ Py_ssize_t str_size = strnlen (str_buf, QUAD_STR_WIDTH);
780+
781+ npy_packed_static_string *out_ps = (npy_packed_static_string *)out_ptr;
782+ if (NpyString_pack (allocator, out_ps, str_buf, (size_t )str_size) < 0 ) {
783+ NpyString_release_allocator (allocator);
784+ PyErr_SetString (PyExc_MemoryError, " Failed to pack string in Quad to StringDType cast" );
785+ return -1 ;
786+ }
787+
788+ in_ptr += in_stride;
789+ out_ptr += out_stride;
790+ }
791+
792+ NpyString_release_allocator (allocator);
793+ return 0 ;
794+ }
795+
608796// Tag dispatching to ensure npy_bool/npy_ubyte and npy_half/npy_ushort do not alias in templates
609797// see e.g. https://stackoverflow.com/q/32522279
610798struct spec_npy_bool {};
@@ -1395,6 +1583,44 @@ init_casts_internal(void)
13951583 };
13961584 add_spec (quad_to_bytes_spec);
13971585
1586+ // StringDType to QuadPrecision cast
1587+ PyArray_DTypeMeta **stringdtype_to_quad_dtypes = new PyArray_DTypeMeta *[2 ]{&PyArray_StringDType, &QuadPrecDType};
1588+ PyType_Slot *stringdtype_to_quad_slots = new PyType_Slot[4 ]{
1589+ {NPY_METH_resolve_descriptors, (void *)&stringdtype_to_quad_resolve_descriptors},
1590+ {NPY_METH_strided_loop, (void *)&stringdtype_to_quad_strided_loop<true >},
1591+ {NPY_METH_unaligned_strided_loop, (void *)&stringdtype_to_quad_strided_loop<false >},
1592+ {0 , nullptr }};
1593+
1594+ PyArrayMethod_Spec *stringdtype_to_quad_spec = new PyArrayMethod_Spec{
1595+ .name = " cast_StringDType_to_QuadPrec" ,
1596+ .nin = 1 ,
1597+ .nout = 1 ,
1598+ .casting = NPY_UNSAFE_CASTING,
1599+ .flags = static_cast <NPY_ARRAYMETHOD_FLAGS>(NPY_METH_SUPPORTS_UNALIGNED | NPY_METH_REQUIRES_PYAPI),
1600+ .dtypes = stringdtype_to_quad_dtypes,
1601+ .slots = stringdtype_to_quad_slots,
1602+ };
1603+ add_spec (stringdtype_to_quad_spec);
1604+
1605+ // QuadPrecision to StringDType cast
1606+ PyArray_DTypeMeta **quad_to_stringdtype_dtypes = new PyArray_DTypeMeta *[2 ]{&QuadPrecDType, &PyArray_StringDType};
1607+ PyType_Slot *quad_to_stringdtype_slots = new PyType_Slot[4 ]{
1608+ {NPY_METH_resolve_descriptors, (void *)&quad_to_stringdtype_resolve_descriptors},
1609+ {NPY_METH_strided_loop, (void *)&quad_to_stringdtype_strided_loop<true >},
1610+ {NPY_METH_unaligned_strided_loop, (void *)&quad_to_stringdtype_strided_loop<false >},
1611+ {0 , nullptr }};
1612+
1613+ PyArrayMethod_Spec *quad_to_stringdtype_spec = new PyArrayMethod_Spec{
1614+ .name = " cast_QuadPrec_to_StringDType" ,
1615+ .nin = 1 ,
1616+ .nout = 1 ,
1617+ .casting = NPY_SAFE_CASTING,
1618+ .flags = static_cast <NPY_ARRAYMETHOD_FLAGS>(NPY_METH_SUPPORTS_UNALIGNED | NPY_METH_REQUIRES_PYAPI),
1619+ .dtypes = quad_to_stringdtype_dtypes,
1620+ .slots = quad_to_stringdtype_slots,
1621+ };
1622+ add_spec (quad_to_stringdtype_spec);
1623+
13981624 specs[spec_count] = nullptr ;
13991625 return specs;
14001626}
0 commit comments