@@ -365,6 +365,74 @@ quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars)
365365
366366}
367367
368+ /* *
369+ * Same-value check for quad to string conversions.
370+ * Works for bytes (S), unicode (U), and StringDType.
371+ *
372+ * Performs a roundtrip: quad -> string -> quad and verifies the value is preserved.
373+ * The check uses the truncated string (up to str_len chars) to ensure the output
374+ * buffer can faithfully represent the original value.
375+ *
376+ * @param in_val The input quad value
377+ * @param str_buf The string representation (may be longer than str_len)
378+ * @param str_len Length of the string that will actually be stored (excluding null terminator)
379+ * @param backend The quad backend type
380+ * @return 1 if same_value check passes, -1 if it fails (sets Python error)
381+ */
382+ static inline int
383+ quad_to_string_same_value_check (quad_value in_val, const char *str_buf, npy_intp str_len,
384+ QuadBackendType backend)
385+ {
386+ char *truncated_str = (char *)malloc (str_len + 1 );
387+ if (truncated_str == NULL ) {
388+ PyErr_NoMemory ();
389+ return -1 ;
390+ }
391+ memcpy (truncated_str, str_buf, str_len);
392+ truncated_str[str_len] = ' \0 ' ;
393+
394+ // Parse the truncated string back to quad
395+ quad_value roundtrip;
396+ char *endptr;
397+
398+ int err = NumPyOS_ascii_strtoq (truncated_str, backend, &roundtrip, &endptr);
399+ if (err < 0 ) {
400+ PyErr_Format (PyExc_ValueError,
401+ " QuadPrecision value cannot be represented exactly: string '%s' failed to parse back" ,
402+ truncated_str);
403+ free (truncated_str);
404+ return -1 ;
405+ }
406+ free (truncated_str);
407+
408+ // Compare original and roundtripped values
409+ if (backend == BACKEND_SLEEF ) {
410+ // NaN == NaN for same_value purposes
411+ if (Sleef_iunordq1 (in_val.sleef_value , roundtrip.sleef_value ))
412+ return 1 ;
413+ if (Sleef_icmpeqq1 (in_val.sleef_value , roundtrip.sleef_value ))
414+ return 1 ;
415+ // Handle -0.0 == +0.0 case
416+ if (Sleef_icmpeqq1 (in_val.sleef_value , QUAD_ZERO ) &&
417+ Sleef_icmpeqq1 (roundtrip.sleef_value , QUAD_ZERO ))
418+ return 1 ;
419+ }
420+ else {
421+ if (std::isnan (in_val.longdouble_value ) && std::isnan (roundtrip.longdouble_value ))
422+ return 1 ;
423+ if (in_val.longdouble_value == roundtrip.longdouble_value )
424+ return 1 ;
425+ // Handle -0.0 == +0.0 case
426+ if (in_val.longdouble_value == 0 .0L && roundtrip.longdouble_value == 0 .0L )
427+ return 1 ;
428+ }
429+
430+ PyErr_Format (PyExc_ValueError,
431+ " QuadPrecision value cannot be represented exactly in target string dtype "
432+ " (string width too narrow or precision loss occurred)" );
433+ return -1 ;
434+ }
435+
368436template <bool Aligned>
369437static int
370438quad_to_unicode_loop (PyArrayMethod_Context *context, char *const data[],
@@ -382,37 +450,37 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[],
382450 QuadBackendType backend = descr_in->backend ;
383451
384452 npy_intp unicode_size_chars = descrs[1 ]->elsize / 4 ;
453+ int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG ) == NPY_SAME_VALUE_CONTEXT_FLAG );
385454
386455 while (N--) {
387456 quad_value in_val = load_quad<Aligned>(in_ptr, backend);
388457
389458 // Convert to Sleef_quad for Dragon4
390459 Sleef_quad sleef_val = quad_to_sleef_quad (&in_val, backend);
391460
392- // Get string representation with adaptive notation
393- PyObject *py_str = quad_to_string_adaptive (&sleef_val, unicode_size_chars);
394- if (py_str == NULL ) {
461+ const char *temp_str = quad_to_string_adaptive_cstr (&sleef_val, unicode_size_chars);
462+ if (temp_str == NULL ) {
395463 return -1 ;
396464 }
397465
398- const char *temp_str = PyUnicode_AsUTF8 (py_str);
399- if (temp_str == NULL ) {
400- Py_DECREF (py_str);
401- return -1 ;
466+ npy_intp str_len = strnlen (temp_str, unicode_size_chars);
467+
468+ // Perform same_value check if requested
469+ if (same_value_casting) {
470+ if (quad_to_string_same_value_check (in_val, temp_str, str_len, backend) < 0 ) {
471+ return -1 ;
472+ }
402473 }
403474
404475 // Convert char string to UCS4 and store in output
405476 Py_UCS4 *out_ucs4 = (Py_UCS4 *)out_ptr;
406- npy_intp str_len = strnlen (temp_str, unicode_size_chars);
407477 for (npy_intp i = 0 ; i < str_len; i++) {
408478 out_ucs4[i] = (Py_UCS4)temp_str[i];
409479 }
410480 for (npy_intp i = str_len; i < unicode_size_chars; i++) {
411481 out_ucs4[i] = 0 ;
412482 }
413483
414- Py_DECREF (py_str);
415-
416484 in_ptr += in_stride;
417485 out_ptr += out_stride;
418486 }
@@ -575,25 +643,29 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[],
575643 QuadBackendType backend = descr_in->backend ;
576644
577645 npy_intp bytes_size = descrs[1 ]->elsize ;
646+ int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG ) == NPY_SAME_VALUE_CONTEXT_FLAG );
578647
579648 while (N--) {
580649 quad_value in_val = load_quad<Aligned>(in_ptr, backend);
581650 Sleef_quad sleef_val = quad_to_sleef_quad (&in_val, backend);
582- PyObject *py_str = quad_to_string_adaptive (&sleef_val, bytes_size);
583- if (py_str == NULL ) {
584- return -1 ;
585- }
586- const char *temp_str = PyUnicode_AsUTF8 (py_str);
651+
652+ const char *temp_str = quad_to_string_adaptive_cstr (&sleef_val, bytes_size);
587653 if (temp_str == NULL ) {
588- Py_DECREF (py_str);
589654 return -1 ;
590655 }
591656
657+ npy_intp str_len = strnlen (temp_str, bytes_size);
658+
659+ // Perform same_value check if requested
660+ if (same_value_casting) {
661+ if (quad_to_string_same_value_check (in_val, temp_str, str_len, backend) < 0 ) {
662+ return -1 ;
663+ }
664+ }
665+
592666 // Copy string to output buffer, padding with nulls
593667 strncpy (out_ptr, temp_str, bytes_size);
594668
595- Py_DECREF (py_str);
596-
597669 in_ptr += in_stride;
598670 out_ptr += out_stride;
599671 }
@@ -726,6 +798,7 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat
726798 QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0 ];
727799 PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[1 ];
728800 QuadBackendType backend = descr_in->backend ;
801+ int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG ) == NPY_SAME_VALUE_CONTEXT_FLAG );
729802
730803 npy_string_allocator *allocator = NpyString_acquire_allocator (str_descr);
731804
@@ -743,6 +816,14 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat
743816
744817 Py_ssize_t str_size = strnlen (str_buf, QUAD_STR_WIDTH );
745818
819+ // Perform same_value check if requested
820+ if (same_value_casting) {
821+ if (quad_to_string_same_value_check (in_val, str_buf, str_size, backend) < 0 ) {
822+ NpyString_release_allocator (allocator);
823+ return -1 ;
824+ }
825+ }
826+
746827 npy_packed_static_string *out_ps = (npy_packed_static_string *)out_ptr;
747828 if (NpyString_pack (allocator, out_ps, str_buf, (size_t )str_size) < 0 ) {
748829 NpyString_release_allocator (allocator);
0 commit comments