From fe04b3b9844052b0d7182fc7e4f0e72ef7bf38a5 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Mon, 8 Dec 2025 14:41:53 +0000 Subject: [PATCH 01/41] updating TARGET_VERSION and numpy_to_quad resove desc --- quaddtype/numpy_quaddtype/src/casts.cpp | 5 +++-- quaddtype/numpy_quaddtype/src/dragon4.c | 2 +- quaddtype/numpy_quaddtype/src/dtype.c | 2 +- quaddtype/numpy_quaddtype/src/quaddtype_main.c | 2 +- quaddtype/numpy_quaddtype/src/scalar_ops.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/matmul.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/umath.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp | 2 +- quaddtype/numpy_quaddtype/src/umath/unary_props.cpp | 2 +- 11 files changed, 13 insertions(+), 12 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 659d6daf..e2aa8689 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC @@ -858,7 +858,8 @@ numpy_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta } loop_descrs[0] = PyArray_GetDefaultDescr(dtypes[0]); - return NPY_SAFE_CASTING; + // since QUAD precision is the highest precision, we can always cast to it + return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } template diff --git a/quaddtype/numpy_quaddtype/src/dragon4.c b/quaddtype/numpy_quaddtype/src/dragon4.c index b9a896c8..b006c9b0 100644 --- a/quaddtype/numpy_quaddtype/src/dragon4.c +++ b/quaddtype/numpy_quaddtype/src/dragon4.c @@ -17,7 +17,7 @@ Modifications are specific to support the SLEEF_QUAD #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/dtype.c b/quaddtype/numpy_quaddtype/src/dtype.c index 8a7e1e2b..36e66942 100644 --- a/quaddtype/numpy_quaddtype/src/dtype.c +++ b/quaddtype/numpy_quaddtype/src/dtype.c @@ -7,7 +7,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC #include "numpy/arrayobject.h" diff --git a/quaddtype/numpy_quaddtype/src/quaddtype_main.c b/quaddtype/numpy_quaddtype/src/quaddtype_main.c index b268077a..4aaa6a51 100644 --- a/quaddtype/numpy_quaddtype/src/quaddtype_main.c +++ b/quaddtype/numpy_quaddtype/src/quaddtype_main.c @@ -6,7 +6,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #include "numpy/arrayobject.h" #include "numpy/dtype_api.h" diff --git a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp index ef0fa843..2927d414 100644 --- a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp @@ -1,6 +1,6 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY extern "C" { diff --git a/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp index 23bd52a0..003da813 100644 --- a/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp index 9cf45071..456323ac 100644 --- a/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/comparison_ops.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/umath/matmul.cpp b/quaddtype/numpy_quaddtype/src/umath/matmul.cpp index d377e9f4..416afa0a 100644 --- a/quaddtype/numpy_quaddtype/src/umath/matmul.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/matmul.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/umath/umath.cpp b/quaddtype/numpy_quaddtype/src/umath/umath.cpp index a075c4b8..68734b70 100644 --- a/quaddtype/numpy_quaddtype/src/umath/umath.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/umath.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp index d30483f3..b83ecb88 100644 --- a/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/unary_ops.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC diff --git a/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp b/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp index 7e399ad4..fd285a0f 100644 --- a/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/unary_props.cpp @@ -1,7 +1,7 @@ #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION -#define NPY_TARGET_VERSION NPY_2_0_API_VERSION +#define NPY_TARGET_VERSION NPY_2_4_API_VERSION #define NO_IMPORT_ARRAY #define NO_IMPORT_UFUNC From 9b4d83d95226f0d373d34c7b50aa6d9e716b48ee Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Wed, 24 Dec 2025 11:39:20 +0530 Subject: [PATCH 02/41] quad2quad --- quaddtype/numpy_quaddtype/src/casts.cpp | 22 ++++++++++++------- .../src/quadblas_interface.cpp | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 5352167e..457fd925 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -36,25 +36,31 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), QuadPrecDTypeObject *given_descrs[2], QuadPrecDTypeObject *loop_descrs[2], npy_intp *view_offset) { - NPY_CASTING casting = NPY_NO_CASTING; - Py_INCREF(given_descrs[0]); loop_descrs[0] = given_descrs[0]; if (given_descrs[1] == NULL) { Py_INCREF(given_descrs[0]); loop_descrs[1] = given_descrs[0]; + *view_offset = 0; + return NPY_NO_CASTING; } - else { - Py_INCREF(given_descrs[1]); - loop_descrs[1] = given_descrs[1]; - if (given_descrs[0]->backend != given_descrs[1]->backend) { - casting = NPY_UNSAFE_CASTING; + + Py_INCREF(given_descrs[1]); + loop_descrs[1] = given_descrs[1]; + + if (given_descrs[0]->backend != given_descrs[1]->backend) { + // Different backends require actual conversion, no view possible + *view_offset = NPY_MIN_INTP; + if (given_descrs[0]->backend == BACKEND_SLEEF) { + return NPY_UNSAFE_CASTING; // SLEEF -> long double may lose precision } + // long double -> SLEEF preserves value exactly + return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } *view_offset = 0; - return casting; + return NPY_NO_CASTING; } static int diff --git a/quaddtype/numpy_quaddtype/src/quadblas_interface.cpp b/quaddtype/numpy_quaddtype/src/quadblas_interface.cpp index b51851a3..9d8c7627 100644 --- a/quaddtype/numpy_quaddtype/src/quadblas_interface.cpp +++ b/quaddtype/numpy_quaddtype/src/quadblas_interface.cpp @@ -3,7 +3,7 @@ #include #ifndef DISABLE_QUADBLAS -#include "../subprojects/qblas/include/quadblas/quadblas.hpp" +#include "quadblas/quadblas.hpp" #endif // DISABLE_QUADBLAS extern "C" { From 95a253a75fcce1aaf167604f336e5e4c9b0ad28a Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 24 Dec 2025 15:48:47 +0000 Subject: [PATCH 03/41] fix heisenbugs --- quaddtype/numpy_quaddtype/src/casts.cpp | 110 +++++++--------------- quaddtype/numpy_quaddtype/src/utilities.h | 2 +- quaddtype/tests/test_quaddtype.py | 32 +++++++ 3 files changed, 67 insertions(+), 77 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 457fd925..4fa33c6a 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -26,6 +26,7 @@ extern "C" { #include "lock.h" #include "dragon4.h" #include "ops.hpp" +#include "constants.hpp" #define NUM_CASTS 40 // 18 to_casts + 18 from_casts + 1 quad_to_quad + 1 void_to_quad #define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer @@ -53,7 +54,7 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), // Different backends require actual conversion, no view possible *view_offset = NPY_MIN_INTP; if (given_descrs[0]->backend == BACKEND_SLEEF) { - return NPY_UNSAFE_CASTING; // SLEEF -> long double may lose precision + return NPY_SAME_KIND_CASTING; // SLEEF -> long double may lose precision } // long double -> SLEEF preserves value exactly return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); @@ -63,10 +64,11 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), return NPY_NO_CASTING; } +template static int -quad_to_quad_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) +quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], + npy_intp const dimensions[], npy_intp const strides[], + void *NPY_UNUSED(auxdata)) { npy_intp N = dimensions[0]; char *in_ptr = data[0]; @@ -76,94 +78,50 @@ quad_to_quad_strided_loop_unaligned(PyArrayMethod_Context *context, char *const QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)context->descriptors[0]; QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)context->descriptors[1]; + QuadBackendType backend_in = descr_in->backend; + QuadBackendType backend_out = descr_out->backend; // inter-backend casting - if (descr_in->backend != descr_out->backend) { + if (backend_in != backend_out) { while (N--) { - quad_value in_val, out_val; - if (descr_in->backend == BACKEND_SLEEF) { - memcpy(&in_val.sleef_value, in_ptr, sizeof(Sleef_quad)); - out_val.longdouble_value = Sleef_cast_to_doubleq1(in_val.sleef_value); + quad_value in_val = load_quad(in_ptr, backend_in); + if (backend_in == BACKEND_SLEEF) + { + long double res = Sleef_cast_to_doubleq1(in_val.sleef_value); + store(out_ptr, res); } - else { - memcpy(&in_val.longdouble_value, in_ptr, sizeof(long double)); - out_val.sleef_value = Sleef_cast_from_doubleq1(in_val.longdouble_value); + else + { + Sleef_quad res; + long double ld = in_val.longdouble_value; + if (std::isnan(ld)) { + res = QUAD_PRECISION_NAN; + } + else if (std::isinf(ld)) { + res = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else + { + res = Sleef_cast_from_doubleq1(static_cast(ld)); + } + store(out_ptr, res); } - memcpy(out_ptr, &out_val, - (descr_out->backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) - : sizeof(long double)); in_ptr += in_stride; out_ptr += out_stride; } - return 0; } - size_t elem_size = - (descr_in->backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - + // same backend: direct copy while (N--) { - memcpy(out_ptr, in_ptr, elem_size); + quad_value val = load_quad(in_ptr, backend_in); + store_quad(out_ptr, val, backend_out); in_ptr += in_stride; out_ptr += out_stride; } return 0; } -static int -quad_to_quad_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) -{ - npy_intp N = dimensions[0]; - char *in_ptr = data[0]; - char *out_ptr = data[1]; - npy_intp in_stride = strides[0]; - npy_intp out_stride = strides[1]; - - QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)context->descriptors[0]; - QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)context->descriptors[1]; - - // inter-backend casting - if (descr_in->backend != descr_out->backend) { - if (descr_in->backend == BACKEND_SLEEF) { - while (N--) { - Sleef_quad in_val = *(Sleef_quad *)in_ptr; - *(long double *)out_ptr = Sleef_cast_to_doubleq1(in_val); - in_ptr += in_stride; - out_ptr += out_stride; - } - } - else { - while (N--) { - long double in_val = *(long double *)in_ptr; - *(Sleef_quad *)out_ptr = Sleef_cast_from_doubleq1(in_val); - in_ptr += in_stride; - out_ptr += out_stride; - } - } - - return 0; - } - - if (descr_in->backend == BACKEND_SLEEF) { - while (N--) { - *(Sleef_quad *)out_ptr = *(Sleef_quad *)in_ptr; - in_ptr += in_stride; - out_ptr += out_stride; - } - } - else { - while (N--) { - *(long double *)out_ptr = *(long double *)in_ptr; - in_ptr += in_stride; - out_ptr += out_stride; - } - } - - return 0; -} - static NPY_CASTING void_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2], PyArray_Descr *given_descrs[2], PyArray_Descr *loop_descrs[2], @@ -1447,8 +1405,8 @@ init_casts_internal(void) PyArray_DTypeMeta **quad2quad_dtypes = new PyArray_DTypeMeta *[2]{nullptr, nullptr}; PyType_Slot *quad2quad_slots = new PyType_Slot[4]{ {NPY_METH_resolve_descriptors, (void *)&quad_to_quad_resolve_descriptors}, - {NPY_METH_strided_loop, (void *)&quad_to_quad_strided_loop_aligned}, - {NPY_METH_unaligned_strided_loop, (void *)&quad_to_quad_strided_loop_unaligned}, + {NPY_METH_strided_loop, (void *)&quad_to_quad_strided_loop}, + {NPY_METH_unaligned_strided_loop, (void *)&quad_to_quad_strided_loop}, {0, nullptr}}; PyArrayMethod_Spec *quad2quad_spec = new PyArrayMethod_Spec{ diff --git a/quaddtype/numpy_quaddtype/src/utilities.h b/quaddtype/numpy_quaddtype/src/utilities.h index f3513328..8d70e125 100644 --- a/quaddtype/numpy_quaddtype/src/utilities.h +++ b/quaddtype/numpy_quaddtype/src/utilities.h @@ -48,7 +48,7 @@ load(const char *ptr) // Store a value to memory, handling alignment template static inline void -store(char *ptr, T val) +store(char *ptr, const T &val) { if constexpr (Aligned) { *(T *)ptr = val; diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index dabe9744..54ff64c9 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5368,3 +5368,35 @@ def test_hash_backends(self, backend): """Test hash works for both backends.""" quad_val = QuadPrecision(1.5, backend=backend) assert hash(quad_val) == hash(1.5) + + +@pytest.mark.parametrize("src_backend,dst_backend", [ + ("sleef", "longdouble"), + ("longdouble", "sleef"), + ("sleef", "sleef"), + ("longdouble", "longdouble"), +]) +@pytest.mark.parametrize("value", [ + "0.0", "-0.0", "1.0", "-1.0", "3.14159265358979323846", + "inf", "-inf", "nan", "1e100", "1e-100", +]) +def test_quad_to_quad_backend_casting(src_backend, dst_backend, value): + """Test casting between QuadPrecDType with different backends.""" + + src_arr = np.array([value], dtype=QuadPrecDType(backend=src_backend)) + dst_arr = src_arr.astype(QuadPrecDType(backend=dst_backend)) + res_arr = np.array([value], dtype=QuadPrecDType(backend=dst_backend)) + + expected_backend = 0 if dst_backend == 'sleef' else 1 + assert dst_arr.dtype.backend == expected_backend + + if np.isnan(src_arr[0]): + assert np.isnan(dst_arr[0]) + elif np.isinf(src_arr[0]): + assert np.isinf(dst_arr[0]) + elif src_backend != dst_backend: + np.testing.assert_allclose(dst_arr, res_arr, rtol=1e-15) + else: + np.testing.assert_array_equal(dst_arr, res_arr) + + From 277ee7b10ba2f5c80c1045be4ba4816445eb7f7d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Wed, 24 Dec 2025 16:23:57 +0000 Subject: [PATCH 04/41] refactor aligned/unaligned into templates --- quaddtype/numpy_quaddtype/src/casts.cpp | 101 ++++-------------------- 1 file changed, 16 insertions(+), 85 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 4fa33c6a..d99ce428 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1014,39 +1014,11 @@ numpy_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } -template +template static int -numpy_to_quad_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) -{ - npy_intp N = dimensions[0]; - char *in_ptr = data[0]; - char *out_ptr = data[1]; - - QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)context->descriptors[1]; - QuadBackendType backend = descr_out->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - - while (N--) { - typename NpyType::TYPE in_val; - quad_value out_val; - - memcpy(&in_val, in_ptr, sizeof(typename NpyType::TYPE)); - out_val = to_quad(in_val, backend); - memcpy(out_ptr, &out_val, elem_size); - - in_ptr += strides[0]; - out_ptr += strides[1]; - } - return 0; -} - -template -static int -numpy_to_quad_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) +numpy_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], + npy_intp const dimensions[], npy_intp const strides[], + void *NPY_UNUSED(auxdata)) { npy_intp N = dimensions[0]; char *in_ptr = data[0]; @@ -1056,15 +1028,9 @@ numpy_to_quad_strided_loop_aligned(PyArrayMethod_Context *context, char *const d QuadBackendType backend = descr_out->backend; while (N--) { - typename NpyType::TYPE in_val = *(typename NpyType::TYPE *)in_ptr; + typename NpyType::TYPE in_val = load::TYPE>(in_ptr); quad_value out_val = to_quad(in_val, backend); - - if (backend == BACKEND_SLEEF) { - *(Sleef_quad *)(out_ptr) = out_val.sleef_value; - } - else { - *(long double *)(out_ptr) = out_val.longdouble_value; - } + store_quad(out_ptr, out_val, backend); in_ptr += strides[0]; out_ptr += strides[1]; @@ -1274,39 +1240,11 @@ quad_to_numpy_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta return NPY_UNSAFE_CASTING; } -template +template static int -quad_to_numpy_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) -{ - npy_intp N = dimensions[0]; - char *in_ptr = data[0]; - char *out_ptr = data[1]; - - QuadPrecDTypeObject *quad_descr = (QuadPrecDTypeObject *)context->descriptors[0]; - QuadBackendType backend = quad_descr->backend; - - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - - while (N--) { - quad_value in_val; - memcpy(&in_val, in_ptr, elem_size); - - typename NpyType::TYPE out_val = from_quad(in_val, backend); - memcpy(out_ptr, &out_val, sizeof(typename NpyType::TYPE)); - - in_ptr += strides[0]; - out_ptr += strides[1]; - } - return 0; -} - -template -static int -quad_to_numpy_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[], - npy_intp const dimensions[], npy_intp const strides[], - void *NPY_UNUSED(auxdata)) +quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], + npy_intp const dimensions[], npy_intp const strides[], + void *NPY_UNUSED(auxdata)) { npy_intp N = dimensions[0]; char *in_ptr = data[0]; @@ -1316,16 +1254,9 @@ quad_to_numpy_strided_loop_aligned(PyArrayMethod_Context *context, char *const d QuadBackendType backend = quad_descr->backend; while (N--) { - quad_value in_val; - if (backend == BACKEND_SLEEF) { - in_val.sleef_value = *(Sleef_quad *)in_ptr; - } - else { - in_val.longdouble_value = *(long double *)in_ptr; - } - + quad_value in_val = load_quad(in_ptr, backend); typename NpyType::TYPE out_val = from_quad(in_val, backend); - *(typename NpyType::TYPE *)(out_ptr) = out_val; + store::TYPE>(out_ptr, out_val); in_ptr += strides[0]; out_ptr += strides[1]; @@ -1358,8 +1289,8 @@ add_cast_from(PyArray_DTypeMeta *to) PyType_Slot *slots = new PyType_Slot[]{ {NPY_METH_resolve_descriptors, (void *)&quad_to_numpy_resolve_descriptors}, - {NPY_METH_strided_loop, (void *)&quad_to_numpy_strided_loop_aligned}, - {NPY_METH_unaligned_strided_loop, (void *)&quad_to_numpy_strided_loop_unaligned}, + {NPY_METH_strided_loop, (void *)&quad_to_numpy_strided_loop}, + {NPY_METH_unaligned_strided_loop, (void *)&quad_to_numpy_strided_loop}, {0, nullptr}}; PyArrayMethod_Spec *spec = new PyArrayMethod_Spec{ @@ -1382,8 +1313,8 @@ add_cast_to(PyArray_DTypeMeta *from) PyType_Slot *slots = new PyType_Slot[]{ {NPY_METH_resolve_descriptors, (void *)&numpy_to_quad_resolve_descriptors}, - {NPY_METH_strided_loop, (void *)&numpy_to_quad_strided_loop_aligned}, - {NPY_METH_unaligned_strided_loop, (void *)&numpy_to_quad_strided_loop_unaligned}, + {NPY_METH_strided_loop, (void *)&numpy_to_quad_strided_loop}, + {NPY_METH_unaligned_strided_loop, (void *)&numpy_to_quad_strided_loop}, {0, nullptr}}; PyArrayMethod_Spec *spec = new PyArrayMethod_Spec{ From 5cb4e4a25db5d7431ee43b39afaddab3712ab7c4 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 01:09:37 +0530 Subject: [PATCH 05/41] resolve desc + quad2numpy loop fix --- quaddtype/numpy_quaddtype/src/casts.cpp | 70 +++++++++++++++++++++---- quaddtype/tests/test_quaddtype.py | 8 +++ 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index d99ce428..2a90b6e4 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -15,6 +15,7 @@ extern "C" { } #include #include +#include #include "sleef.h" #include "sleefquad.h" @@ -169,7 +170,7 @@ unicode_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMet loop_descrs[1] = given_descrs[1]; } - return NPY_UNSAFE_CASTING; + return static_cast(NPY_UNSAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // Helper function: Convert UCS4 string to quad_value @@ -293,9 +294,9 @@ quad_to_unicode_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMet // If target descriptor is wide enough, it's a safe cast if (loop_descrs[1]->elsize >= required_size_bytes) { - return NPY_SAFE_CASTING; + return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } - return NPY_SAME_KIND_CASTING; + return static_cast(NPY_SAME_KIND_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // Helper function: Convert quad to string with adaptive notation @@ -441,7 +442,7 @@ bytes_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta loop_descrs[1] = given_descrs[1]; } - return NPY_UNSAFE_CASTING; + return static_cast(NPY_UNSAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // Helper function: Convert bytes string to quad_value @@ -552,9 +553,9 @@ quad_to_bytes_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta // If target descriptor is wide enough, it's a safe cast if (loop_descrs[1]->elsize >= required_size_bytes) { - return NPY_SAFE_CASTING; + return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } - return NPY_SAME_KIND_CASTING; + return static_cast(NPY_UNSAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } template @@ -620,7 +621,7 @@ stringdtype_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTyp Py_INCREF(given_descrs[0]); loop_descrs[0] = given_descrs[0]; - return NPY_UNSAFE_CASTING; + return static_cast(NPY_UNSAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // Note: StringDType elements are always aligned, so Aligned template parameter @@ -704,7 +705,7 @@ quad_to_stringdtype_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTyp Py_INCREF(given_descrs[0]); loop_descrs[0] = given_descrs[0]; - return NPY_SAFE_CASTING; + return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // Note: StringDType elements are always aligned, so Aligned template parameter @@ -1227,6 +1228,36 @@ from_quad(quad_value x, QuadBackendType backend) } } +template +static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType backend, typename NpyType::TYPE *y) +{ + *y = from_quad(x, backend); + quad_value roundtrip = to_quad(*y, backend); + if(backend == BACKEND_SLEEF) { + if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) + return 1; + } + else { + if(x.longdouble_value == roundtrip.longdouble_value) + return 1; + } + PyErr_SetString(PyExc_ValueError, "could not cast 'same_value' to QuadType"); + return -1; +} + +// Type trait to check if a type is a floating-point type for casting purposes +template +struct is_float_type : std::false_type {}; + +template <> +struct is_float_type : std::true_type {}; +template <> +struct is_float_type : std::true_type {}; +template <> +struct is_float_type : std::true_type {}; +template <> +struct is_float_type : std::true_type {}; + template static NPY_CASTING quad_to_numpy_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *dtypes[2], @@ -1237,7 +1268,13 @@ quad_to_numpy_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta loop_descrs[0] = given_descrs[0]; loop_descrs[1] = PyArray_GetDefaultDescr(dtypes[1]); - return NPY_UNSAFE_CASTING; + // For floating-point types: same_kind casting (precision loss but same kind) + if constexpr (is_float_type::value) { + return static_cast(NPY_SAME_KIND_CASTING | NPY_SAME_VALUE_CASTING_FLAG); + } else { + // For integer/bool types: unsafe casting (cross-kind conversion) + return static_cast(NPY_UNSAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); + } } template @@ -1252,7 +1289,22 @@ quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], QuadPrecDTypeObject *quad_descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = quad_descr->backend; + int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); + if (same_value_casting) { + while (N--) { + quad_value in_val = load_quad(in_ptr, backend); + typename NpyType::TYPE out_val; + int ret = quad_to_numpy_same_value_check(in_val, backend, &out_val); + if(ret < 0) + return -1; + store::TYPE>(out_ptr, out_val); + + in_ptr += strides[0]; + out_ptr += strides[1]; + } + return 0; + } while (N--) { quad_value in_val = load_quad(in_ptr, backend); typename NpyType::TYPE out_val = from_quad(in_val, backend); diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 54ff64c9..252323b9 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5400,3 +5400,11 @@ def test_quad_to_quad_backend_casting(src_backend, dst_backend, value): np.testing.assert_array_equal(dst_arr, res_arr) +def test_same_value_cast(): + a = np.arange(30, dtype=np.float32) + # upcasting can never fail + b = a.astype(QuadPrecision, casting='same_value') + c = b.astype(np.float32, casting='same_value') + assert np.all(c == a) + with pytest.raises(ValueError, match="could not cast 'same_value'"): + (b + 1e22).astype(np.float32, casting='same_value') From 33825659a86b087f90daab163b9265a83111542d Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 13:42:02 +0530 Subject: [PATCH 06/41] adding same_value int tests --- quaddtype/tests/test_quaddtype.py | 82 ++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 252323b9..2e2f1173 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5399,12 +5399,78 @@ def test_quad_to_quad_backend_casting(src_backend, dst_backend, value): else: np.testing.assert_array_equal(dst_arr, res_arr) +class TestSameValueCasting: + """Test 'same_value' casting behavior for QuadPrecision.""" + def test_same_value_cast(self): + a = np.arange(30, dtype=np.float32) + # upcasting can never fail + b = a.astype(QuadPrecision, casting='same_value') + c = b.astype(np.float32, casting='same_value') + assert np.all(c == a) + with pytest.raises(ValueError, match="could not cast 'same_value'"): + (b + 1e22).astype(np.float32, casting='same_value') -def test_same_value_cast(): - a = np.arange(30, dtype=np.float32) - # upcasting can never fail - b = a.astype(QuadPrecision, casting='same_value') - c = b.astype(np.float32, casting='same_value') - assert np.all(c == a) - with pytest.raises(ValueError, match="could not cast 'same_value'"): - (b + 1e22).astype(np.float32, casting='same_value') + + @pytest.mark.parametrize("dtype,passing,failing", [ + # bool: only 0 and 1 are valid + ("bool", [0, 1], [2, -1, 0.5]), + + # int8: [-128, 127] + ("int8", [-128, 0, 127], [-129, 128, 1.5]), + + # uint8: [0, 255] + ("uint8", [0, 255], [-1, 256, 2.5]), + + # int16: [-32768, 32767] + ("int16", [-32768, 0, 32767], [-32769, 32768, 0.1]), + + # uint16: [0, 65535] + ("uint16", [0, 65535], [-1, 65536]), + + # int32: [-2^31, 2^31-1] + ("int32", [-2**31, 0, 2**31 - 1], [-2**31 - 1, 2**31]), + + # uint32: [0, 2^32-1] + ("uint32", [0, 2**32 - 1], [-1, 2**32]), + + # int64: [-2^63, 2^63-1] + ("int64", [-2**63, 0, 2**63 - 1], [-2**63 - 1, 2**63]), + + # uint64: [0, 2^64-1] + ("uint64", [0, 2**64 - 1], [-1, 2**64]), + ]) + def test_same_value_cast_quad_to_int(self, dtype, passing, failing): + """A 128-bit float can represent all consecutive integers exactly up to 2^113""" + for val in passing: + q = np.array([val], dtype=QuadPrecDType()) + result = q.astype(dtype, casting="same_value") + assert result == val + + for val in failing: + q = np.array([val], dtype=QuadPrecDType()) + with pytest.raises(ValueError, match="could not cast 'same_value'"): + q.astype(dtype, casting="same_value") + + @pytest.mark.parametrize("dtype", ["half", "float16", + "float", "float32", + "double", "float64", + "longdouble",]) + @pytest.mark.parametrize("values", [ + + ]) + def test_same_value_cast_floats(self, dtype, values): + pass + + @pytest.mark.parametrize("dtype", [ + "S50", "U50", "U50", "S100", "U100", "U100", np.dtypes.StringDType()]) + @pytest.mark.parametrize("values", [ + ]) + def test_same_value_cast_strings_enough_width(self, dtype, values): + pass + + @pytest.mark.parametrize("dtype", [ + "S20", "U20", "U20"]) + @pytest.mark.parametrize("values", [ + ]) + def test_same_value_cast_strings_small_width(self, dtype, values): + pass \ No newline at end of file From 534a64ef4015db5143e158e45ebad892c7b75a41 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 13:59:31 +0530 Subject: [PATCH 07/41] handling nan in same_value --- quaddtype/numpy_quaddtype/src/casts.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 2a90b6e4..6b710471 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1234,14 +1234,28 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b *y = from_quad(x, backend); quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) + if(Sleef_iunordq1(x.sleef_value, roundtrip.sleef_value)) + return 1; + else if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) return 1; } else { - if(x.longdouble_value == roundtrip.longdouble_value) + if(std::isnan(x.longdouble_value) && std::isnan(roundtrip.longdouble_value)) + return 1; + else if(x.longdouble_value == roundtrip.longdouble_value) return 1; } - PyErr_SetString(PyExc_ValueError, "could not cast 'same_value' to QuadType"); + Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); + if (val_str != NULL) { + PyErr_Format(PyExc_ValueError, + "QuadPrecision value '%s' cannot be represented exactly in the target dtype", + val_str); + } + else { + PyErr_SetString(PyExc_ValueError, + "QuadPrecision value cannot be represented exactly in the target dtype"); + } return -1; } From f696848c1ef63b0c0cef869c14dff18c13416228 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 14:27:39 +0530 Subject: [PATCH 08/41] fix tests --- quaddtype/numpy_quaddtype/src/casts.cpp | 21 +++++++++++++++++-- quaddtype/tests/test_quaddtype.py | 28 ++----------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 6b710471..af8d7da3 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1236,13 +1236,13 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b if(backend == BACKEND_SLEEF) { if(Sleef_iunordq1(x.sleef_value, roundtrip.sleef_value)) return 1; - else if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) + if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) return 1; } else { if(std::isnan(x.longdouble_value) && std::isnan(roundtrip.longdouble_value)) return 1; - else if(x.longdouble_value == roundtrip.longdouble_value) + if(x.longdouble_value == roundtrip.longdouble_value) return 1; } Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); @@ -1259,6 +1259,23 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b return -1; } +// template +// static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType backend, typename NpyType::TYPE *y) +// { +// *y = from_quad(x, backend); +// quad_value roundtrip = to_quad(*y, backend); +// if(backend == BACKEND_SLEEF) { +// if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) +// return 1; +// } +// else { +// if(x.longdouble_value == roundtrip.longdouble_value) +// return 1; +// } +// PyErr_SetString(PyExc_ValueError, "could not cast 'same_value' to QuadType"); +// return -1; +// } + // Type trait to check if a type is a floating-point type for casting purposes template struct is_float_type : std::false_type {}; diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 2e2f1173..5c204624 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5448,29 +5448,5 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing): for val in failing: q = np.array([val], dtype=QuadPrecDType()) - with pytest.raises(ValueError, match="could not cast 'same_value'"): - q.astype(dtype, casting="same_value") - - @pytest.mark.parametrize("dtype", ["half", "float16", - "float", "float32", - "double", "float64", - "longdouble",]) - @pytest.mark.parametrize("values", [ - - ]) - def test_same_value_cast_floats(self, dtype, values): - pass - - @pytest.mark.parametrize("dtype", [ - "S50", "U50", "U50", "S100", "U100", "U100", np.dtypes.StringDType()]) - @pytest.mark.parametrize("values", [ - ]) - def test_same_value_cast_strings_enough_width(self, dtype, values): - pass - - @pytest.mark.parametrize("dtype", [ - "S20", "U20", "U20"]) - @pytest.mark.parametrize("values", [ - ]) - def test_same_value_cast_strings_small_width(self, dtype, values): - pass \ No newline at end of file + with pytest.raises(ValueError): + q.astype(dtype, casting="same_value") \ No newline at end of file From 22327f8434f4b7595e6b8926c1188c130a43a30d Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 14:40:05 +0530 Subject: [PATCH 09/41] again union hesinbug? --- quaddtype/numpy_quaddtype/src/casts.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index af8d7da3..7c656958 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1245,7 +1245,8 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b if(x.longdouble_value == roundtrip.longdouble_value) return 1; } - Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + Sleef_quad sleef_val = x.sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, @@ -1259,23 +1260,6 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b return -1; } -// template -// static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType backend, typename NpyType::TYPE *y) -// { -// *y = from_quad(x, backend); -// quad_value roundtrip = to_quad(*y, backend); -// if(backend == BACKEND_SLEEF) { -// if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) -// return 1; -// } -// else { -// if(x.longdouble_value == roundtrip.longdouble_value) -// return 1; -// } -// PyErr_SetString(PyExc_ValueError, "could not cast 'same_value' to QuadType"); -// return -1; -// } - // Type trait to check if a type is a floating-point type for casting purposes template struct is_float_type : std::false_type {}; From 6fa020d5d097ed7764ae9328c0ab46823b4baf91 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 14:44:17 +0530 Subject: [PATCH 10/41] just match with valueerror --- quaddtype/tests/test_quaddtype.py | 99 ++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 2 deletions(-) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 5c204624..4e249d19 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5407,7 +5407,7 @@ def test_same_value_cast(self): b = a.astype(QuadPrecision, casting='same_value') c = b.astype(np.float32, casting='same_value') assert np.all(c == a) - with pytest.raises(ValueError, match="could not cast 'same_value'"): + with pytest.raises(ValueError): (b + 1e22).astype(np.float32, casting='same_value') @@ -5449,4 +5449,99 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing): for val in failing: q = np.array([val], dtype=QuadPrecDType()) with pytest.raises(ValueError): - q.astype(dtype, casting="same_value") \ No newline at end of file + q.astype(dtype, casting="same_value") + + # @pytest.mark.parametrize("dtype,passing,failing", [ + # # float16/half: 11-bit significand (10 explicit + 1 implicit) + # # max: 65504, exact integers up to 2^11 = 2048 + # ("float16", + # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), + # 1.0, -1.0, 0.5, 0.25, + # 2048.0, # 2^11, largest consecutive integer + # 65504.0, # max representable + # 2**-14, # min positive normal + # ], + # [65536.0, # overflow (> max) + # # precision loss (first non-representable int > 2048) + # 2049.0, + # 1.0 + 2**-11, # precision loss (step from 1.0 is 2^-10) + # ]), + + # ("half", # alias, same values + # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 2048.0], + # [65536.0, 2049.0]), + + # # float32: 24-bit significand (23 explicit + 1 implicit) + # # max: ~3.4e38, exact integers up to 2^24 = 16777216 + # ("float32", + # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), + # 1.0, -1.0, 0.5, 0.25, + # 16777216.0, # 2^24, largest consecutive integer + # 3.4028235e38, # max representable (approx) + # 2**-126, # min positive normal + # ], + # [16777217.0, # precision loss (first non-representable int) + # 1e39, # overflow + # 1.0 + 2**-24, # precision loss (step from 1.0 is 2^-23) + # ]), + + # ("float", # alias + # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 16777216.0], + # [16777217.0, 1e39]), + + # # float64: 53-bit significand (52 explicit + 1 implicit) + # # max: ~1.8e308, exact integers up to 2^53 + # ("float64", + # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), + # 1.0, -1.0, 0.5, 0.25, + # 2.0**53, # largest consecutive integer + # 1.7976931348623157e308, # max representable (approx) + # 2**-1022, # min positive normal + # ], + # [2.0**53 + 1, # precision loss + # 1e309, # overflow + # 1.0 + 2**-53, # precision loss (step from 1.0 is 2^-52) + # ]), + + # ("double", # alias + # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 2.0**53], + # [2.0**53 + 1, 1e309]), + + # # longdouble: platform-dependent! + # # x86 Linux: 80-bit extended, 64-bit significand → integers up to 2^64 + # # Windows/macOS: often same as float64 + # # Consider using np.finfo(np.longdouble).nmant to adjust dynamically + # # ("longdouble", + # # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), + # # 1.0, 2.0**53, 2.0**53 + 1], # 2^53+1 passes if longdouble > float64 + # # # Failing cases depend on platform - maybe skip or parametrize separately + # # []), + # ]) + # def test_same_value_cast_floats(self, dtype, passing, failing): + # for val in passing: + # q = np.array([val], dtype=QuadPrecDType()) + # result = q.astype(dtype, casting="same_value") + # # Use appropriate comparison for nan + # if np.isnan(val): + # assert np.isnan(result) + # else: + # assert result == val + + # for val in failing: + # q = np.array([val], dtype=QuadPrecDType()) + # with pytest.raises(ValueError): + # q.astype(dtype, casting="same_value") + + # @pytest.mark.parametrize("dtype", [ + # "S50", "U50", "U50", "S100", "U100", "U100", np.dtypes.StringDType()]) + # @pytest.mark.parametrize("values", [ + # ]) + # def test_same_value_cast_strings_enough_width(self, dtype, values): + # pass + + # @pytest.mark.parametrize("dtype", [ + # "S20", "U20", "U20"]) + # @pytest.mark.parametrize("values", [ + # ]) + # def test_same_value_cast_strings_small_width(self, dtype, values): + # pass \ No newline at end of file From 0babf9df9274732c6ad1a50e4b742ce58745f189 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 14:58:28 +0530 Subject: [PATCH 11/41] use memcpy --- quaddtype/numpy_quaddtype/src/casts.cpp | 4 ++-- quaddtype/numpy_quaddtype/src/utilities.c | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 7c656958..1101a766 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1245,8 +1245,8 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b if(x.longdouble_value == roundtrip.longdouble_value) return 1; } - // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); - Sleef_quad sleef_val = x.sleef_value; + Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + // Sleef_quad sleef_val = x.sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, diff --git a/quaddtype/numpy_quaddtype/src/utilities.c b/quaddtype/numpy_quaddtype/src/utilities.c index 1842a7ff..33f07987 100644 --- a/quaddtype/numpy_quaddtype/src/utilities.c +++ b/quaddtype/numpy_quaddtype/src/utilities.c @@ -262,9 +262,13 @@ Sleef_quad quad_to_sleef_quad(const quad_value *in_val, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return in_val->sleef_value; + // can directly return but that causes union heisenbugs, + // but this helper is rare to use, so acceptable + Sleef_quad result; + memcpy(&result, &in_val->sleef_value, sizeof(Sleef_quad)); + return result; } else { - return Sleef_cast_from_doubleq1(in_val->longdouble_value); + return Sleef_cast_from_doubleq1((double)(in_val->longdouble_value)); } } \ No newline at end of file From a5cf12431fa608f7e3942143ee14d6fb296a6d4e Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 15:06:08 +0530 Subject: [PATCH 12/41] use memcmp --- quaddtype/numpy_quaddtype/src/casts.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 1101a766..ab036cfb 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1234,9 +1234,13 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b *y = from_quad(x, backend); quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - if(Sleef_iunordq1(x.sleef_value, roundtrip.sleef_value)) + // Use memcmp for exact bit-wise comparison to avoid SLEEF comparison function issues + // on different platforms (especially x86-64 where Sleef_quad may be __float128) + if (std::memcmp(&x.sleef_value, &roundtrip.sleef_value, sizeof(Sleef_quad)) == 0) return 1; - if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) + // Also check for NaN == NaN case (NaN bits won't match but both are NaN) + if (Sleef_iunordq1(x.sleef_value, x.sleef_value) && + Sleef_iunordq1(roundtrip.sleef_value, roundtrip.sleef_value)) return 1; } else { @@ -1246,7 +1250,6 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b return 1; } Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); - // Sleef_quad sleef_val = x.sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, From 90b824dca300cc1066e8f9391e0430606f4b5e90 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 15:13:24 +0530 Subject: [PATCH 13/41] switch back to no union --- quaddtype/numpy_quaddtype/src/casts.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index ab036cfb..7c656958 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1234,13 +1234,9 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b *y = from_quad(x, backend); quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - // Use memcmp for exact bit-wise comparison to avoid SLEEF comparison function issues - // on different platforms (especially x86-64 where Sleef_quad may be __float128) - if (std::memcmp(&x.sleef_value, &roundtrip.sleef_value, sizeof(Sleef_quad)) == 0) + if(Sleef_iunordq1(x.sleef_value, roundtrip.sleef_value)) return 1; - // Also check for NaN == NaN case (NaN bits won't match but both are NaN) - if (Sleef_iunordq1(x.sleef_value, x.sleef_value) && - Sleef_iunordq1(roundtrip.sleef_value, roundtrip.sleef_value)) + if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) return 1; } else { @@ -1249,7 +1245,8 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b if(x.longdouble_value == roundtrip.longdouble_value) return 1; } - Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); + Sleef_quad sleef_val = x.sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, From ff69b8e806b59abcef1d1659a84d77ba9fb23d77 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 17:14:57 +0530 Subject: [PATCH 14/41] addded float tests --- quaddtype/numpy_quaddtype/src/casts.cpp | 6 + quaddtype/tests/test_quaddtype.py | 148 +++++++++++------------- 2 files changed, 74 insertions(+), 80 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 7c656958..e57c7b7a 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1238,12 +1238,18 @@ static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType b return 1; if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) return 1; + // Handle -0.0 == +0.0 case: both zeros are considered equal for same_value casting + if(Sleef_icmpeqq1(x.sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + return 1; } else { if(std::isnan(x.longdouble_value) && std::isnan(roundtrip.longdouble_value)) return 1; if(x.longdouble_value == roundtrip.longdouble_value) return 1; + // Handle -0.0 == +0.0 case for longdouble backend + if(x.longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + return 1; } // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); Sleef_quad sleef_val = x.sleef_value; diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 4e249d19..e5047207 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5451,86 +5451,74 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing): with pytest.raises(ValueError): q.astype(dtype, casting="same_value") - # @pytest.mark.parametrize("dtype,passing,failing", [ - # # float16/half: 11-bit significand (10 explicit + 1 implicit) - # # max: 65504, exact integers up to 2^11 = 2048 - # ("float16", - # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), - # 1.0, -1.0, 0.5, 0.25, - # 2048.0, # 2^11, largest consecutive integer - # 65504.0, # max representable - # 2**-14, # min positive normal - # ], - # [65536.0, # overflow (> max) - # # precision loss (first non-representable int > 2048) - # 2049.0, - # 1.0 + 2**-11, # precision loss (step from 1.0 is 2^-10) - # ]), - - # ("half", # alias, same values - # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 2048.0], - # [65536.0, 2049.0]), - - # # float32: 24-bit significand (23 explicit + 1 implicit) - # # max: ~3.4e38, exact integers up to 2^24 = 16777216 - # ("float32", - # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), - # 1.0, -1.0, 0.5, 0.25, - # 16777216.0, # 2^24, largest consecutive integer - # 3.4028235e38, # max representable (approx) - # 2**-126, # min positive normal - # ], - # [16777217.0, # precision loss (first non-representable int) - # 1e39, # overflow - # 1.0 + 2**-24, # precision loss (step from 1.0 is 2^-23) - # ]), - - # ("float", # alias - # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 16777216.0], - # [16777217.0, 1e39]), - - # # float64: 53-bit significand (52 explicit + 1 implicit) - # # max: ~1.8e308, exact integers up to 2^53 - # ("float64", - # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), - # 1.0, -1.0, 0.5, 0.25, - # 2.0**53, # largest consecutive integer - # 1.7976931348623157e308, # max representable (approx) - # 2**-1022, # min positive normal - # ], - # [2.0**53 + 1, # precision loss - # 1e309, # overflow - # 1.0 + 2**-53, # precision loss (step from 1.0 is 2^-52) - # ]), - - # ("double", # alias - # [0.0, float('inf'), float('-inf'), float('nan'), 1.0, 2.0**53], - # [2.0**53 + 1, 1e309]), - - # # longdouble: platform-dependent! - # # x86 Linux: 80-bit extended, 64-bit significand → integers up to 2^64 - # # Windows/macOS: often same as float64 - # # Consider using np.finfo(np.longdouble).nmant to adjust dynamically - # # ("longdouble", - # # [0.0, -0.0, float('inf'), float('-inf'), float('nan'), - # # 1.0, 2.0**53, 2.0**53 + 1], # 2^53+1 passes if longdouble > float64 - # # # Failing cases depend on platform - maybe skip or parametrize separately - # # []), - # ]) - # def test_same_value_cast_floats(self, dtype, passing, failing): - # for val in passing: - # q = np.array([val], dtype=QuadPrecDType()) - # result = q.astype(dtype, casting="same_value") - # # Use appropriate comparison for nan - # if np.isnan(val): - # assert np.isnan(result) - # else: - # assert result == val - - # for val in failing: - # q = np.array([val], dtype=QuadPrecDType()) - # with pytest.raises(ValueError): - # q.astype(dtype, casting="same_value") + @pytest.mark.parametrize("dtype", [ + np.float16, np.float32, np.float64, np.longdouble + ]) + def test_same_value_cast_floats_special_values(self, dtype): + """Test that special floating-point values roundtrip correctly.""" + special_values = [0.0, -0.0, float('inf'), float('-inf'), float('nan')] + + for val in special_values: + q = np.array([val], dtype=QuadPrecDType()) + result = q.astype(dtype, casting="same_value") + if np.isnan(val): + assert np.isnan(result), f"NaN failed for {dtype}" + else: + assert result == val, f"{val} failed for {dtype}" + + @pytest.mark.parametrize("dtype", [ + np.float16, np.float32, np.float64, np.longdouble + ]) + def test_same_value_cast_floats_within_range(self, dtype): + """Test values that should roundtrip exactly within dtype's precision.""" + info = np.finfo(dtype) + + # Values that should pass (exactly representable) + passing_values = [ + 1.0, -1.0, 0.5, -0.5, 0.25, -0.25, + 2.0, 4.0, 8.0, # powers of 2 + info.tiny, # min positive normal + 2 ** info.nmant, # largest consecutive integer + ] + + for val in passing_values: + # Ensure the value is representable in the target dtype first + target_val = dtype(val) + q = np.array([target_val], dtype=QuadPrecDType()) + result = q.astype(dtype, casting="same_value") + assert result == target_val, f"Value {val} failed for {dtype}" + + + @pytest.mark.parametrize("dtype", [ + np.float16, np.float32, np.float64, np.longdouble + ]) + def test_same_value_cast_floats_precision_loss(self, dtype): + """Test values that cannot be represented exactly and should fail.""" + from decimal import Decimal, getcontext + + getcontext().prec = 50 # plenty for quad precision + info = np.finfo(dtype) + nmant = info.nmant # 10 for f16, 23 for f32, 52 for f64 + + # First odd integer beyond exact representability + first_bad_int = 2 ** (nmant + 1) + 1 + # Value between 1.0 and 1.0 + eps (i.e., 1 + eps/2) + # eps = 2^-nmant, so eps/2 = 2^-(nmant+1) + one_plus_half_eps = Decimal(1) + Decimal(2) ** -(nmant + 1) + + # Value between 2.0 and 2.0 + 2*eps + two_plus_eps = Decimal(2) + Decimal(2) ** -nmant + + failing_values = [ + str(first_bad_int), + str(one_plus_half_eps), + str(two_plus_eps), + ] + + for val in failing_values: + q = np.array([val], dtype=QuadPrecDType()) + with pytest.raises(ValueError): + q.astype(dtype, casting="same_value") # @pytest.mark.parametrize("dtype", [ # "S50", "U50", "U50", "S100", "U100", "U100", np.dtypes.StringDType()]) From 30f6b95a4cc2e5206d19ed9e103dfb150f826f7b Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 17:34:20 +0530 Subject: [PATCH 15/41] use double's tiny in ld --- quaddtype/tests/test_quaddtype.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index e5047207..15a030aa 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5477,10 +5477,19 @@ def test_same_value_cast_floats_within_range(self, dtype): passing_values = [ 1.0, -1.0, 0.5, -0.5, 0.25, -0.25, 2.0, 4.0, 8.0, # powers of 2 - info.tiny, # min positive normal 2 ** info.nmant, # largest consecutive integer ] + # For longdouble on x86-64, info.tiny can be ~3.36e-4932, which is outside + # the double range (~2.2e-308). Since SLEEF backend converts quad <-> longdouble + # via double (Sleef_cast_to/from_doubleq1), values outside double's range + # cannot roundtrip correctly. Use double's tiny for longdouble in this case. + double_info = np.finfo(np.float64) + if dtype == np.longdouble and info.tiny < double_info.tiny: + passing_values.append(double_info.tiny) + else: + passing_values.append(info.tiny) + for val in passing_values: # Ensure the value is representable in the target dtype first target_val = dtype(val) From 5c5d791f2672b9580f61ca4ae5ee70bb7c7dfbed Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 18:11:03 +0530 Subject: [PATCH 16/41] adding quad->str same_vale --- quaddtype/numpy_quaddtype/src/casts.cpp | 117 ++++++++++++++++++++---- quaddtype/tests/test_quaddtype.py | 58 +++++++++--- 2 files changed, 144 insertions(+), 31 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index e57c7b7a..f7edb4f7 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -365,6 +365,74 @@ quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars) } +/** + * Same-value check for quad to string conversions. + * Works for bytes (S), unicode (U), and StringDType. + * + * Performs a roundtrip: quad -> string -> quad and verifies the value is preserved. + * The check uses the truncated string (up to str_len chars) to ensure the output + * buffer can faithfully represent the original value. + * + * @param in_val The input quad value + * @param str_buf The string representation (may be longer than str_len) + * @param str_len Length of the string that will actually be stored (excluding null terminator) + * @param backend The quad backend type + * @return 1 if same_value check passes, -1 if it fails (sets Python error) + */ +static inline int +quad_to_string_same_value_check(quad_value in_val, const char *str_buf, npy_intp str_len, + QuadBackendType backend) +{ + char *truncated_str = (char *)malloc(str_len + 1); + if (truncated_str == NULL) { + PyErr_NoMemory(); + return -1; + } + memcpy(truncated_str, str_buf, str_len); + truncated_str[str_len] = '\0'; + + // Parse the truncated string back to quad + quad_value roundtrip; + char *endptr; + + int err = NumPyOS_ascii_strtoq(truncated_str, backend, &roundtrip, &endptr); + if (err < 0) { + PyErr_Format(PyExc_ValueError, + "QuadPrecision value cannot be represented exactly: string '%s' failed to parse back", + truncated_str); + free(truncated_str); + return -1; + } + free(truncated_str); + + // Compare original and roundtripped values + if (backend == BACKEND_SLEEF) { + // NaN == NaN for same_value purposes + if (Sleef_iunordq1(in_val.sleef_value, roundtrip.sleef_value)) + return 1; + if (Sleef_icmpeqq1(in_val.sleef_value, roundtrip.sleef_value)) + return 1; + // Handle -0.0 == +0.0 case + if (Sleef_icmpeqq1(in_val.sleef_value, QUAD_ZERO) && + Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + return 1; + } + else { + if (std::isnan(in_val.longdouble_value) && std::isnan(roundtrip.longdouble_value)) + return 1; + if (in_val.longdouble_value == roundtrip.longdouble_value) + return 1; + // Handle -0.0 == +0.0 case + if (in_val.longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + return 1; + } + + PyErr_Format(PyExc_ValueError, + "QuadPrecision value cannot be represented exactly in target string dtype " + "(string width too narrow or precision loss occurred)"); + return -1; +} + template static int quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], @@ -382,6 +450,7 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], QuadBackendType backend = descr_in->backend; npy_intp unicode_size_chars = descrs[1]->elsize / 4; + int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); while (N--) { quad_value in_val = load_quad(in_ptr, backend); @@ -389,21 +458,22 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], // Convert to Sleef_quad for Dragon4 Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); - // Get string representation with adaptive notation - PyObject *py_str = quad_to_string_adaptive(&sleef_val, unicode_size_chars); - if (py_str == NULL) { + const char *temp_str = quad_to_string_adaptive_cstr(&sleef_val, unicode_size_chars); + if (temp_str == NULL) { return -1; } - const char *temp_str = PyUnicode_AsUTF8(py_str); - if (temp_str == NULL) { - Py_DECREF(py_str); - return -1; + npy_intp str_len = strnlen(temp_str, unicode_size_chars); + + // Perform same_value check if requested + if (same_value_casting) { + if (quad_to_string_same_value_check(in_val, temp_str, str_len, backend) < 0) { + return -1; + } } // Convert char string to UCS4 and store in output Py_UCS4 *out_ucs4 = (Py_UCS4 *)out_ptr; - npy_intp str_len = strnlen(temp_str, unicode_size_chars); for (npy_intp i = 0; i < str_len; i++) { out_ucs4[i] = (Py_UCS4)temp_str[i]; } @@ -411,8 +481,6 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], out_ucs4[i] = 0; } - Py_DECREF(py_str); - in_ptr += in_stride; out_ptr += out_stride; } @@ -575,25 +643,29 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[], QuadBackendType backend = descr_in->backend; npy_intp bytes_size = descrs[1]->elsize; + int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); while (N--) { quad_value in_val = load_quad(in_ptr, backend); Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); - PyObject *py_str = quad_to_string_adaptive(&sleef_val, bytes_size); - if (py_str == NULL) { - return -1; - } - const char *temp_str = PyUnicode_AsUTF8(py_str); + + const char *temp_str = quad_to_string_adaptive_cstr(&sleef_val, bytes_size); if (temp_str == NULL) { - Py_DECREF(py_str); return -1; } + npy_intp str_len = strnlen(temp_str, bytes_size); + + // Perform same_value check if requested + if (same_value_casting) { + if (quad_to_string_same_value_check(in_val, temp_str, str_len, backend) < 0) { + return -1; + } + } + // Copy string to output buffer, padding with nulls strncpy(out_ptr, temp_str, bytes_size); - Py_DECREF(py_str); - in_ptr += in_stride; out_ptr += out_stride; } @@ -726,6 +798,7 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)descrs[0]; PyArray_StringDTypeObject *str_descr = (PyArray_StringDTypeObject *)descrs[1]; QuadBackendType backend = descr_in->backend; + int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); npy_string_allocator *allocator = NpyString_acquire_allocator(str_descr); @@ -743,6 +816,14 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat Py_ssize_t str_size = strnlen(str_buf, QUAD_STR_WIDTH); + // Perform same_value check if requested + if (same_value_casting) { + if (quad_to_string_same_value_check(in_val, str_buf, str_size, backend) < 0) { + NpyString_release_allocator(allocator); + return -1; + } + } + npy_packed_static_string *out_ps = (npy_packed_static_string *)out_ptr; if (NpyString_pack(allocator, out_ps, str_buf, (size_t)str_size) < 0) { NpyString_release_allocator(allocator); diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 15a030aa..6942c8ed 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5529,16 +5529,48 @@ def test_same_value_cast_floats_precision_loss(self, dtype): with pytest.raises(ValueError): q.astype(dtype, casting="same_value") - # @pytest.mark.parametrize("dtype", [ - # "S50", "U50", "U50", "S100", "U100", "U100", np.dtypes.StringDType()]) - # @pytest.mark.parametrize("values", [ - # ]) - # def test_same_value_cast_strings_enough_width(self, dtype, values): - # pass - - # @pytest.mark.parametrize("dtype", [ - # "S20", "U20", "U20"]) - # @pytest.mark.parametrize("values", [ - # ]) - # def test_same_value_cast_strings_small_width(self, dtype, values): - # pass \ No newline at end of file + @pytest.mark.parametrize("dtype", [ + "S50", "U50", " 10 chars + ] + for val in failing_values: + q = np.array([val], dtype=QuadPrecDType()) + with pytest.raises(ValueError): + q.astype(dtype, casting="same_value") \ No newline at end of file From dde4a844c23faa1614e0a71d42b51043ac50a6a9 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Thu, 25 Dec 2025 18:20:31 +0530 Subject: [PATCH 17/41] improve error msg --- quaddtype/numpy_quaddtype/src/casts.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index f7edb4f7..cf665046 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -427,9 +427,21 @@ quad_to_string_same_value_check(quad_value in_val, const char *str_buf, npy_intp return 1; } - PyErr_Format(PyExc_ValueError, - "QuadPrecision value cannot be represented exactly in target string dtype " - "(string width too narrow or precision loss occurred)"); + // Values don't match - the string width is too narrow for exact representation + // Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); + Sleef_quad sleef_val = in_val.sleef_value; + const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); + if (val_str != NULL) { + PyErr_Format(PyExc_ValueError, + "QuadPrecision value '%s' cannot be represented exactly in target string dtype " + "(string width too narrow or precision loss occurred)", + val_str); + } + else { + PyErr_SetString(PyExc_ValueError, + "QuadPrecision value cannot be represented exactly in target string dtype " + "(string width too narrow or precision loss occurred)"); + } return -1; } From 8862c23c3853c6d702663d114eca0e6264527641 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 15:05:24 +0000 Subject: [PATCH 18/41] make all from_quad uses const pointer to union --- quaddtype/numpy_quaddtype/src/casts.cpp | 121 +++++++++++----------- quaddtype/numpy_quaddtype/src/utilities.h | 2 +- quaddtype/tests/test_quaddtype.py | 110 ++++++++++---------- 3 files changed, 117 insertions(+), 116 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index cf665046..fc6667a7 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -365,7 +365,7 @@ quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars) } -/** +/* * Same-value check for quad to string conversions. * Works for bytes (S), unicode (U), and StringDType. * @@ -1136,216 +1136,219 @@ numpy_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], template static inline typename NpyType::TYPE -from_quad(quad_value x, QuadBackendType backend); +from_quad(const quad_value *x, QuadBackendType backend); template <> inline npy_bool -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return Sleef_cast_to_int64q1(x.sleef_value) != 0; + return Sleef_cast_to_int64q1(x->sleef_value) != 0; } else { - return x.longdouble_value != 0; + return x->longdouble_value != 0; } } template <> inline npy_byte -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { // runtime warnings often comes from/to casting of NaN, inf // casting is used by ops at several positions leading to warnings // fix can be catching the cases and returning corresponding type value without casting if (backend == BACKEND_SLEEF) { - return (npy_byte)Sleef_cast_to_int64q1(x.sleef_value); + return (npy_byte)Sleef_cast_to_int64q1(x->sleef_value); } else { - return (npy_byte)x.longdouble_value; + return (npy_byte)x->longdouble_value; } } template <> inline npy_ubyte -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_ubyte)Sleef_cast_to_uint64q1(x.sleef_value); + return (npy_ubyte)Sleef_cast_to_uint64q1(x->sleef_value); } else { - return (npy_ubyte)x.longdouble_value; + return (npy_ubyte)x->longdouble_value; } } template <> inline npy_short -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_short)Sleef_cast_to_int64q1(x.sleef_value); + return (npy_short)Sleef_cast_to_int64q1(x->sleef_value); } else { - return (npy_short)x.longdouble_value; + return (npy_short)x->longdouble_value; } } template <> inline npy_ushort -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_ushort)Sleef_cast_to_uint64q1(x.sleef_value); + return (npy_ushort)Sleef_cast_to_uint64q1(x->sleef_value); } else { - return (npy_ushort)x.longdouble_value; + return (npy_ushort)x->longdouble_value; } } template <> inline npy_int -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_int)Sleef_cast_to_int64q1(x.sleef_value); + return (npy_int)Sleef_cast_to_int64q1(x->sleef_value); } else { - return (npy_int)x.longdouble_value; + return (npy_int)x->longdouble_value; } } template <> inline npy_uint -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_uint)Sleef_cast_to_uint64q1(x.sleef_value); + return (npy_uint)Sleef_cast_to_uint64q1(x->sleef_value); } else { - return (npy_uint)x.longdouble_value; + return (npy_uint)x->longdouble_value; } } template <> inline npy_long -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_long)Sleef_cast_to_int64q1(x.sleef_value); + return (npy_long)Sleef_cast_to_int64q1(x->sleef_value); } else { - return (npy_long)x.longdouble_value; + return (npy_long)x->longdouble_value; } } template <> inline npy_ulong -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (npy_ulong)Sleef_cast_to_uint64q1(x.sleef_value); + return (npy_ulong)Sleef_cast_to_uint64q1(x->sleef_value); } else { - return (npy_ulong)x.longdouble_value; + return (npy_ulong)x->longdouble_value; } } template <> inline npy_longlong -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return Sleef_cast_to_int64q1(x.sleef_value); + return Sleef_cast_to_int64q1(x->sleef_value); } else { - return (npy_longlong)x.longdouble_value; + return (npy_longlong)x->longdouble_value; } } template <> inline npy_ulonglong -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return Sleef_cast_to_uint64q1(x.sleef_value); + return Sleef_cast_to_uint64q1(x->sleef_value); } else { - return (npy_ulonglong)x.longdouble_value; + return (npy_ulonglong)x->longdouble_value; } } template <> inline npy_half -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return npy_double_to_half(Sleef_cast_to_doubleq1(x.sleef_value)); + double d = Sleef_cast_to_doubleq1(x->sleef_value); + return npy_double_to_half(d); } else { - return npy_double_to_half((double)x.longdouble_value); + return npy_double_to_half((double)x->longdouble_value); } } template <> inline float -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (float)Sleef_cast_to_doubleq1(x.sleef_value); + return (float)Sleef_cast_to_doubleq1(x->sleef_value); } else { - return (float)x.longdouble_value; + return (float)x->longdouble_value; } } template <> inline double -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return Sleef_cast_to_doubleq1(x.sleef_value); + return Sleef_cast_to_doubleq1(x->sleef_value); } else { - return (double)x.longdouble_value; + return (double)x->longdouble_value; } } template <> inline long double -from_quad(quad_value x, QuadBackendType backend) +from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (long double)Sleef_cast_to_doubleq1(x.sleef_value); + return (long double)Sleef_cast_to_doubleq1(x->sleef_value); } else { - return x.longdouble_value; + return x->longdouble_value; } } template -static inline int quad_to_numpy_same_value_check(quad_value x, QuadBackendType backend, typename NpyType::TYPE *y) +static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBackendType backend, typename NpyType::TYPE *y) { *y = from_quad(x, backend); quad_value roundtrip = to_quad(*y, backend); - if(backend == BACKEND_SLEEF) { - if(Sleef_iunordq1(x.sleef_value, roundtrip.sleef_value)) + if(backend == BACKEND_SLEEF) + { + if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value)) return 1; - if(Sleef_icmpeqq1(x.sleef_value, roundtrip.sleef_value)) + if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value)) return 1; // Handle -0.0 == +0.0 case: both zeros are considered equal for same_value casting - if(Sleef_icmpeqq1(x.sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + if(Sleef_icmpeqq1(x->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) return 1; } - else { - if(std::isnan(x.longdouble_value) && std::isnan(roundtrip.longdouble_value)) + else + { + if(std::isnan(x->longdouble_value) && std::isnan(roundtrip.longdouble_value)) return 1; - if(x.longdouble_value == roundtrip.longdouble_value) + if(x->longdouble_value == roundtrip.longdouble_value) return 1; // Handle -0.0 == +0.0 case for longdouble backend - if(x.longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + if(x->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) return 1; } // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); - Sleef_quad sleef_val = x.sleef_value; + Sleef_quad sleef_val = x->sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, @@ -1409,7 +1412,7 @@ quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], while (N--) { quad_value in_val = load_quad(in_ptr, backend); typename NpyType::TYPE out_val; - int ret = quad_to_numpy_same_value_check(in_val, backend, &out_val); + int ret = quad_to_numpy_same_value_check(&in_val, backend, &out_val); if(ret < 0) return -1; store::TYPE>(out_ptr, out_val); @@ -1421,7 +1424,7 @@ quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], } while (N--) { quad_value in_val = load_quad(in_ptr, backend); - typename NpyType::TYPE out_val = from_quad(in_val, backend); + typename NpyType::TYPE out_val = from_quad(&in_val, backend); store::TYPE>(out_ptr, out_val); in_ptr += strides[0]; diff --git a/quaddtype/numpy_quaddtype/src/utilities.h b/quaddtype/numpy_quaddtype/src/utilities.h index 8d70e125..52c646e4 100644 --- a/quaddtype/numpy_quaddtype/src/utilities.h +++ b/quaddtype/numpy_quaddtype/src/utilities.h @@ -63,7 +63,7 @@ template static inline quad_value load_quad(const char *ptr, QuadBackendType backend) { - quad_value val; + quad_value val{}; if (backend == BACKEND_SLEEF) { val.sleef_value = load(ptr); } diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 6942c8ed..819bfb82 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5454,17 +5454,15 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing): @pytest.mark.parametrize("dtype", [ np.float16, np.float32, np.float64, np.longdouble ]) - def test_same_value_cast_floats_special_values(self, dtype): + @pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan')]) + def test_same_value_cast_floats_special_values(self, dtype, val): """Test that special floating-point values roundtrip correctly.""" - special_values = [0.0, -0.0, float('inf'), float('-inf'), float('nan')] - - for val in special_values: - q = np.array([val], dtype=QuadPrecDType()) - result = q.astype(dtype, casting="same_value") - if np.isnan(val): - assert np.isnan(result), f"NaN failed for {dtype}" - else: - assert result == val, f"{val} failed for {dtype}" + q = np.array([val], dtype=QuadPrecDType()) + result = q.astype(dtype, casting="same_value") + if np.isnan(val): + assert np.isnan(result), f"NaN failed for {dtype}" + else: + assert result == val, f"{val} failed for {dtype}" @pytest.mark.parametrize("dtype", [ np.float16, np.float32, np.float64, np.longdouble @@ -5495,7 +5493,7 @@ def test_same_value_cast_floats_within_range(self, dtype): target_val = dtype(val) q = np.array([target_val], dtype=QuadPrecDType()) result = q.astype(dtype, casting="same_value") - assert result == target_val, f"Value {val} failed for {dtype}" + assert result[0] == target_val, f"Value {val} failed for {dtype}" @pytest.mark.parametrize("dtype", [ @@ -5529,48 +5527,48 @@ def test_same_value_cast_floats_precision_loss(self, dtype): with pytest.raises(ValueError): q.astype(dtype, casting="same_value") - @pytest.mark.parametrize("dtype", [ - "S50", "U50", " 10 chars - ] - for val in failing_values: - q = np.array([val], dtype=QuadPrecDType()) - with pytest.raises(ValueError): - q.astype(dtype, casting="same_value") \ No newline at end of file + # @pytest.mark.parametrize("dtype", [ + # "S50", "U50", " 10 chars + # ] + # for val in failing_values: + # q = np.array([val], dtype=QuadPrecDType()) + # with pytest.raises(ValueError): + # q.astype(dtype, casting="same_value") \ No newline at end of file From e374a36b3561bec6fbb2693ec597462cc1f6be18 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 15:13:09 +0000 Subject: [PATCH 19/41] fixed string same_value --- quaddtype/numpy_quaddtype/src/casts.cpp | 22 +++--- quaddtype/tests/test_quaddtype.py | 90 ++++++++++++------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index fc6667a7..3210b260 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -380,7 +380,7 @@ quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars) * @return 1 if same_value check passes, -1 if it fails (sets Python error) */ static inline int -quad_to_string_same_value_check(quad_value in_val, const char *str_buf, npy_intp str_len, +quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, npy_intp str_len, QuadBackendType backend) { char *truncated_str = (char *)malloc(str_len + 1); @@ -408,28 +408,28 @@ quad_to_string_same_value_check(quad_value in_val, const char *str_buf, npy_intp // Compare original and roundtripped values if (backend == BACKEND_SLEEF) { // NaN == NaN for same_value purposes - if (Sleef_iunordq1(in_val.sleef_value, roundtrip.sleef_value)) + if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value)) return 1; - if (Sleef_icmpeqq1(in_val.sleef_value, roundtrip.sleef_value)) + if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value)) return 1; // Handle -0.0 == +0.0 case - if (Sleef_icmpeqq1(in_val.sleef_value, QUAD_ZERO) && + if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) return 1; } else { - if (std::isnan(in_val.longdouble_value) && std::isnan(roundtrip.longdouble_value)) + if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) return 1; - if (in_val.longdouble_value == roundtrip.longdouble_value) + if (in_val->longdouble_value == roundtrip.longdouble_value) return 1; // Handle -0.0 == +0.0 case - if (in_val.longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) return 1; } // Values don't match - the string width is too narrow for exact representation // Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); - Sleef_quad sleef_val = in_val.sleef_value; + Sleef_quad sleef_val = in_val->sleef_value; const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, @@ -479,7 +479,7 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], // Perform same_value check if requested if (same_value_casting) { - if (quad_to_string_same_value_check(in_val, temp_str, str_len, backend) < 0) { + if (quad_to_string_same_value_check(&in_val, temp_str, str_len, backend) < 0) { return -1; } } @@ -670,7 +670,7 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[], // Perform same_value check if requested if (same_value_casting) { - if (quad_to_string_same_value_check(in_val, temp_str, str_len, backend) < 0) { + if (quad_to_string_same_value_check(&in_val, temp_str, str_len, backend) < 0) { return -1; } } @@ -830,7 +830,7 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat // Perform same_value check if requested if (same_value_casting) { - if (quad_to_string_same_value_check(in_val, str_buf, str_size, backend) < 0) { + if (quad_to_string_same_value_check(&in_val, str_buf, str_size, backend) < 0) { NpyString_release_allocator(allocator); return -1; } diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 819bfb82..423c5e64 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5527,48 +5527,48 @@ def test_same_value_cast_floats_precision_loss(self, dtype): with pytest.raises(ValueError): q.astype(dtype, casting="same_value") - # @pytest.mark.parametrize("dtype", [ - # "S50", "U50", " 10 chars - # ] - # for val in failing_values: - # q = np.array([val], dtype=QuadPrecDType()) - # with pytest.raises(ValueError): - # q.astype(dtype, casting="same_value") \ No newline at end of file + @pytest.mark.parametrize("dtype", [ + "S50", "U50", " 10 chars + ] + for val in failing_values: + q = np.array([val], dtype=QuadPrecDType()) + with pytest.raises(ValueError): + q.astype(dtype, casting="same_value") \ No newline at end of file From c458d53f686a6721fd99c821cfecd36a9bb1990f Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 15:17:24 +0000 Subject: [PATCH 20/41] use quad2sleefquad --- quaddtype/numpy_quaddtype/src/casts.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 3210b260..4ce9dab9 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -428,8 +428,7 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n } // Values don't match - the string width is too narrow for exact representation - // Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); - Sleef_quad sleef_val = in_val->sleef_value; + Sleef_quad sleef_val = quad_to_sleef_quad(in_val, backend); const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, @@ -1347,8 +1346,7 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken if(x->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) return 1; } - // Sleef_quad sleef_val = quad_to_sleef_quad(&x, backend); - Sleef_quad sleef_val = x->sleef_value; + Sleef_quad sleef_val = quad_to_sleef_quad(x, backend); const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); if (val_str != NULL) { PyErr_Format(PyExc_ValueError, From 9d10144346842c97341ac1d053f0693378f7686b Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 16:02:04 +0000 Subject: [PATCH 21/41] remove non-native order tets for respective systems --- quaddtype/tests/test_quaddtype.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 423c5e64..421d5dd3 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5501,6 +5501,10 @@ def test_same_value_cast_floats_within_range(self, dtype): ]) def test_same_value_cast_floats_precision_loss(self, dtype): """Test values that cannot be represented exactly and should fail.""" + import sys + if dtype == np.longdouble and sys.byteorder == 'big': + pytest.skip("longdouble precision loss test skipped on big-endian systems") + from decimal import Decimal, getcontext getcontext().prec = 50 # plenty for quad precision @@ -5528,7 +5532,7 @@ def test_same_value_cast_floats_precision_loss(self, dtype): q.astype(dtype, casting="same_value") @pytest.mark.parametrize("dtype", [ - "S50", "U50", " Date: Thu, 25 Dec 2025 17:43:28 +0000 Subject: [PATCH 22/41] powerpc has ld as quad --- quaddtype/tests/test_quaddtype.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 421d5dd3..632af3a7 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5502,14 +5502,14 @@ def test_same_value_cast_floats_within_range(self, dtype): def test_same_value_cast_floats_precision_loss(self, dtype): """Test values that cannot be represented exactly and should fail.""" import sys - if dtype == np.longdouble and sys.byteorder == 'big': - pytest.skip("longdouble precision loss test skipped on big-endian systems") - from decimal import Decimal, getcontext getcontext().prec = 50 # plenty for quad precision info = np.finfo(dtype) nmant = info.nmant # 10 for f16, 23 for f32, 52 for f64 + + if dtype == np.longdouble and nmant >= 112: + pytest.skip("longdouble has same precision as quad on this platform") # First odd integer beyond exact representability first_bad_int = 2 ** (nmant + 1) + 1 From ae9986b8e9ba068aeba3ac6c51c8743c5ac52dd2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 19:25:19 +0000 Subject: [PATCH 23/41] memory barrier --- quaddtype/numpy_quaddtype/src/casts.cpp | 53 +++++++++------ quaddtype/numpy_quaddtype/src/utilities.h | 16 ++--- quaddtype/tests/test_quaddtype.py | 82 ++++++++++++++++++++++- 3 files changed, 120 insertions(+), 31 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 4ce9dab9..2f47d507 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -55,7 +55,8 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), // Different backends require actual conversion, no view possible *view_offset = NPY_MIN_INTP; if (given_descrs[0]->backend == BACKEND_SLEEF) { - return NPY_SAME_KIND_CASTING; // SLEEF -> long double may lose precision + // SLEEF -> long double may lose precision + return static_cast(NPY_SAME_KIND_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } // long double -> SLEEF preserves value exactly return static_cast(NPY_SAFE_CASTING | NPY_SAME_VALUE_CASTING_FLAG); @@ -81,32 +82,36 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)context->descriptors[1]; QuadBackendType backend_in = descr_in->backend; QuadBackendType backend_out = descr_out->backend; + int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); // inter-backend casting if (backend_in != backend_out) { while (N--) { - quad_value in_val = load_quad(in_ptr, backend_in); + quad_value in_val; + load_quad(in_ptr, backend_in, &in_val); + quad_value out_val; if (backend_in == BACKEND_SLEEF) { - long double res = Sleef_cast_to_doubleq1(in_val.sleef_value); - store(out_ptr, res); + out_val.longdouble_value = static_cast(Sleef_cast_to_doubleq1(in_val.sleef_value)); } else { - Sleef_quad res; long double ld = in_val.longdouble_value; if (std::isnan(ld)) { - res = QUAD_PRECISION_NAN; + out_val.sleef_value = QUAD_PRECISION_NAN; } else if (std::isinf(ld)) { - res = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + out_val.sleef_value = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; } else { - res = Sleef_cast_from_doubleq1(static_cast(ld)); + // to prevent compiler optimizations, ABI handling issues with __float128 on x86-64 machines + // won't be expensive as for fixed size compiler can optimize memcpy with movq + Sleef_quad temp = Sleef_cast_from_doubleq1(static_cast(ld)); + std::memcpy(&out_val.sleef_value, &temp, sizeof(Sleef_quad)); } - store(out_ptr, res); } + store_quad(out_ptr, &out_val, backend_out); in_ptr += in_stride; out_ptr += out_stride; } @@ -114,9 +119,10 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], } // same backend: direct copy - while (N--) { - quad_value val = load_quad(in_ptr, backend_in); - store_quad(out_ptr, val, backend_out); + while(N--) { + quad_value val; + load_quad(in_ptr, backend_in, &val); + store_quad(out_ptr, &val, backend_out); in_ptr += in_stride; out_ptr += out_stride; } @@ -242,7 +248,7 @@ unicode_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], return -1; } - store_quad(out_ptr, out_val, backend); + store_quad(out_ptr, &out_val, backend); in_ptr += in_stride; out_ptr += out_stride; @@ -464,7 +470,8 @@ quad_to_unicode_loop(PyArrayMethod_Context *context, char *const data[], int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); while (N--) { - quad_value in_val = load_quad(in_ptr, backend); + quad_value in_val; + load_quad(in_ptr, backend, &in_val); // Convert to Sleef_quad for Dragon4 Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); @@ -595,7 +602,7 @@ bytes_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], return -1; } - store_quad(out_ptr, out_val, backend); + store_quad(out_ptr, &out_val, backend); in_ptr += in_stride; out_ptr += out_stride; @@ -657,7 +664,8 @@ quad_to_bytes_loop(PyArrayMethod_Context *context, char *const data[], int same_value_casting = ((context->flags & NPY_SAME_VALUE_CONTEXT_FLAG) == NPY_SAME_VALUE_CONTEXT_FLAG); while (N--) { - quad_value in_val = load_quad(in_ptr, backend); + quad_value in_val; + load_quad(in_ptr, backend, &in_val); Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); const char *temp_str = quad_to_string_adaptive_cstr(&sleef_val, bytes_size); @@ -756,7 +764,7 @@ stringdtype_to_quad_strided_loop(PyArrayMethod_Context *context, char *const dat return -1; } - store_quad(out_ptr, out_val, backend); + store_quad(out_ptr, &out_val, backend); in_ptr += in_stride; out_ptr += out_stride; @@ -814,7 +822,8 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat npy_string_allocator *allocator = NpyString_acquire_allocator(str_descr); while (N--) { - quad_value in_val = load_quad(in_ptr, backend); + quad_value in_val; + load_quad(in_ptr, backend, &in_val); Sleef_quad sleef_val = quad_to_sleef_quad(&in_val, backend); // Get string representation with adaptive notation @@ -1123,7 +1132,7 @@ numpy_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], while (N--) { typename NpyType::TYPE in_val = load::TYPE>(in_ptr); quad_value out_val = to_quad(in_val, backend); - store_quad(out_ptr, out_val, backend); + store_quad(out_ptr, &out_val, backend); in_ptr += strides[0]; out_ptr += strides[1]; @@ -1408,7 +1417,8 @@ quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], if (same_value_casting) { while (N--) { - quad_value in_val = load_quad(in_ptr, backend); + quad_value in_val; + load_quad(in_ptr, backend, &in_val); typename NpyType::TYPE out_val; int ret = quad_to_numpy_same_value_check(&in_val, backend, &out_val); if(ret < 0) @@ -1421,7 +1431,8 @@ quad_to_numpy_strided_loop(PyArrayMethod_Context *context, char *const data[], return 0; } while (N--) { - quad_value in_val = load_quad(in_ptr, backend); + quad_value in_val; + load_quad(in_ptr, backend, &in_val); typename NpyType::TYPE out_val = from_quad(&in_val, backend); store::TYPE>(out_ptr, out_val); diff --git a/quaddtype/numpy_quaddtype/src/utilities.h b/quaddtype/numpy_quaddtype/src/utilities.h index 52c646e4..6a0384c6 100644 --- a/quaddtype/numpy_quaddtype/src/utilities.h +++ b/quaddtype/numpy_quaddtype/src/utilities.h @@ -60,29 +60,27 @@ store(char *ptr, const T &val) // Load quad_value from memory based on backend and alignment template -static inline quad_value -load_quad(const char *ptr, QuadBackendType backend) +static inline void +load_quad(const char *ptr, QuadBackendType backend, quad_value *out) { - quad_value val{}; if (backend == BACKEND_SLEEF) { - val.sleef_value = load(ptr); + out->sleef_value = load(ptr); } else { - val.longdouble_value = load(ptr); + out->longdouble_value = load(ptr); } - return val; } // Store quad_value to memory based on backend and alignment template static inline void -store_quad(char *ptr, const quad_value &val, QuadBackendType backend) +store_quad(char *ptr, const quad_value *val, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - store(ptr, val.sleef_value); + store(ptr, val->sleef_value); } else { - store(ptr, val.longdouble_value); + store(ptr, val->longdouble_value); } } diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 632af3a7..5f823e58 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5575,4 +5575,84 @@ def test_same_value_cast_strings_narrow_width(self, dtype): for val in failing_values: q = np.array([val], dtype=QuadPrecDType()) with pytest.raises(ValueError): - q.astype(dtype, casting="same_value") \ No newline at end of file + q.astype(dtype, casting="same_value") + + # @pytest.mark.parametrize("src_backend,dst_backend", [ + # ("sleef", "longdouble"), + # ("longdouble", "sleef"), + # ("sleef", "sleef"), + # ("longdouble", "longdouble") + # ]) + # def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend): + # """Test values that should roundtrip exactly between backends.""" + # # Values exactly representable in both backends (and in double, since + # # inter-backend conversion goes through double) + # passing_values = [ + # "0.0", "-0.0", "1.0", "-1.0", + # "0.5", "0.25", "0.125", + # "2.0", "4.0", "8.0", + # "inf", "-inf", "nan", + # "1e100", "-1e-100", + # str(2**52), # Largest consecutive integer in double + # ] + + # for val in passing_values: + # src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) + # result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") + + # # Verify value is preserved + # if val == "nan": + # assert np.isnan(result[0]) + # else: + # assert result[0] == src[0], f"Value {val} failed for {src_backend} -> {dst_backend}" + + + # @pytest.mark.parametrize("src_backend,dst_backend", [ + # ("sleef", "longdouble"), + # ("longdouble", "sleef"), + # ]) + # def test_quad_to_quad_interbackend_same_value_casting_failing(self, src_backend, dst_backend): + # """Test values that cannot roundtrip exactly between backends.""" + + # # Inter-backend conversion goes through double, so values outside + # # double's precision should fail + # ld_info = np.finfo(np.longdouble) + + # # Skip if longdouble has same precision as quad (PowerPC binary128) + # if ld_info.nmant >= 112: + # pytest.skip("longdouble has same precision as quad on this platform") + + # # Also need to consider that conversion goes through double + # # So precision is limited by double (~52 bit mantissa) + # double_info = np.finfo(np.float64) + + # # Values that exceed double precision + # failing_values = [ + # str(2**53 + 1), # First integer not exactly representable in double + # "1.0000000000000001", # 1 + small epsilon beyond double precision + # "3.141592653589793238462643383279502884197", # Pi with more than double precision + # ] + + # for val in failing_values: + # src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) + # with pytest.raises(ValueError): + # src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") + + + # @pytest.mark.parametrize("backend", ["sleef", "longdouble"]) + # def test_quad_to_quad_same_backend_always_passes(self, backend): + # """Same backend conversion should always pass same_value.""" + # # Even high-precision values should pass when backend is the same + # values = [ + # "3.141592653589793238462643383279502884197", + # "2.718281828459045235360287471352662497757", + # str(2**113), # Large integer + # "1e4000", # Large exponent (within quad range) + # ] + + # for val in values: + # src = np.array([val], dtype=QuadPrecDType(backend=backend)) + # result = src.astype(QuadPrecDType(backend=backend), casting="same_value") + # # Should not raise, and value should be unchanged + # assert str(result[0]) == str(src[0]) + \ No newline at end of file From 308f136409b01a085df7f3b1ea693687ee7bf8ca Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Thu, 25 Dec 2025 19:33:53 +0000 Subject: [PATCH 24/41] will cont tomorrow from here --- quaddtype/numpy_quaddtype/src/casts.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 2f47d507..9d860bfb 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -66,6 +66,14 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), return NPY_NO_CASTING; } +static inline int +quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_in, + const quad_value *out_val, QuadBackendType backend_out) +{ + // convert output back to input backend for comparison + return 1; +} + template static int quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], @@ -111,6 +119,15 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], std::memcpy(&out_val.sleef_value, &temp, sizeof(Sleef_quad)); } } + + if(same_value_casting) + { + int ret = quad_to_quad_same_value_check(&in_val, backend_in, &out_val, backend_out); + if (ret < 0) { + return -1; + } + } + store_quad(out_ptr, &out_val, backend_out); in_ptr += in_stride; out_ptr += out_stride; From 00acacafdd2807fb48c65550448f64dba435134c Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Fri, 26 Dec 2025 14:24:21 +0530 Subject: [PATCH 25/41] quad2quad same_value --- quaddtype/numpy_quaddtype/src/casts.cpp | 66 +++++++- quaddtype/numpy_quaddtype/src/scalar_ops.cpp | 3 + quaddtype/tests/test_quaddtype.py | 158 ++++++++++--------- 3 files changed, 146 insertions(+), 81 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 9d860bfb..42eb63c3 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -32,6 +32,10 @@ extern "C" { #define NUM_CASTS 40 // 18 to_casts + 18 from_casts + 1 quad_to_quad + 1 void_to_quad #define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer +// forward declarations +static inline const char * +quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars); + static NPY_CASTING quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]), @@ -63,15 +67,67 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), } *view_offset = 0; - return NPY_NO_CASTING; + return static_cast(NPY_NO_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } +// Helper function for quad-to-quad same_value check (inter-backend) static inline int quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_in, const quad_value *out_val, QuadBackendType backend_out) { - // convert output back to input backend for comparison - return 1; + // Convert output back to input backend for comparison + quad_value roundtrip; + + if (backend_in == BACKEND_SLEEF) { + // Input was SLEEF, output is longdouble + // Convert longdouble back to SLEEF for comparison + long double ld = out_val->longdouble_value; + if (std::isnan(ld)) { + roundtrip.sleef_value = QUAD_PRECISION_NAN; + } + else if (std::isinf(ld)) { + roundtrip.sleef_value = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else { + Sleef_quad temp = Sleef_cast_from_doubleq1(static_cast(ld)); + memcpy(&roundtrip.sleef_value, &temp, sizeof(Sleef_quad)); + } + + // Compare in SLEEF domain + if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value)) + return 1; // Both NaN + if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value)) + return 1; // Equal + if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + return 1; // Both zeros + } + else { + // Input was longdouble, output is SLEEF + // Convert SLEEF back to longdouble for comparison + roundtrip.longdouble_value = static_cast(Sleef_cast_to_doubleq1(out_val->sleef_value)); + + // Compare in longdouble domain + if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) + return 1; + if (in_val->longdouble_value == roundtrip.longdouble_value) + return 1; + if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + return 1; + } + + // Values don't match + Sleef_quad sleef_val = quad_to_sleef_quad(in_val, backend_in); + const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); + if (val_str != NULL) { + PyErr_Format(PyExc_ValueError, + "QuadPrecision value '%s' cannot be represented exactly in target backend", + val_str); + } + else { + PyErr_SetString(PyExc_ValueError, + "QuadPrecision value cannot be represented exactly in target backend"); + } + return -1; } template @@ -119,7 +175,8 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], std::memcpy(&out_val.sleef_value, &temp, sizeof(Sleef_quad)); } } - + + // check same_value for inter-backend casts if(same_value_casting) { int ret = quad_to_quad_same_value_check(&in_val, backend_in, &out_val, backend_out); @@ -136,6 +193,7 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], } // same backend: direct copy + // same_value casting not needed here as values are identical while(N--) { quad_value val; load_quad(in_ptr, backend_in, &val); diff --git a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp index 2927d414..e5c4bdf4 100644 --- a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp @@ -134,6 +134,9 @@ quad_richcompare(QuadPrecisionObject *self, PyObject *other, int cmp_op) Py_INCREF(other); other_quad = (QuadPrecisionObject *)other; if (other_quad->backend != backend) { + // we could allow, but this will be bad + // Two values that are different in quad precision, + // might appear equal when converted to double. PyErr_SetString(PyExc_TypeError, "Cannot compare QuadPrecision objects with different backends"); Py_DECREF(other_quad); diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 5f823e58..76fbe0c2 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5577,82 +5577,86 @@ def test_same_value_cast_strings_narrow_width(self, dtype): with pytest.raises(ValueError): q.astype(dtype, casting="same_value") - # @pytest.mark.parametrize("src_backend,dst_backend", [ - # ("sleef", "longdouble"), - # ("longdouble", "sleef"), - # ("sleef", "sleef"), - # ("longdouble", "longdouble") - # ]) - # def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend): - # """Test values that should roundtrip exactly between backends.""" - # # Values exactly representable in both backends (and in double, since - # # inter-backend conversion goes through double) - # passing_values = [ - # "0.0", "-0.0", "1.0", "-1.0", - # "0.5", "0.25", "0.125", - # "2.0", "4.0", "8.0", - # "inf", "-inf", "nan", - # "1e100", "-1e-100", - # str(2**52), # Largest consecutive integer in double - # ] - - # for val in passing_values: - # src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) - # result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") + @pytest.mark.parametrize("src_backend,dst_backend", [ + ("sleef", "longdouble"), + ("longdouble", "sleef"), + ("sleef", "sleef"), + ("longdouble", "longdouble") + ]) + def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend): + """Test values that should roundtrip exactly between backends.""" + # Values exactly representable in both backends (and in double, since + # inter-backend conversion goes through double) + passing_values = [ + 0.0, -0.0, 1.0, -1.0, + 0.5, 0.25, 0.125, + 2.0, 4.0, 8.0, + "inf", "-inf", "nan", + 1e100, -1e-100, + str(2**52), # Largest consecutive integer in double + ] + + for val in passing_values: + src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) + result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") - # # Verify value is preserved - # if val == "nan": - # assert np.isnan(result[0]) - # else: - # assert result[0] == src[0], f"Value {val} failed for {src_backend} -> {dst_backend}" - - - # @pytest.mark.parametrize("src_backend,dst_backend", [ - # ("sleef", "longdouble"), - # ("longdouble", "sleef"), - # ]) - # def test_quad_to_quad_interbackend_same_value_casting_failing(self, src_backend, dst_backend): - # """Test values that cannot roundtrip exactly between backends.""" - - # # Inter-backend conversion goes through double, so values outside - # # double's precision should fail - # ld_info = np.finfo(np.longdouble) - - # # Skip if longdouble has same precision as quad (PowerPC binary128) - # if ld_info.nmant >= 112: - # pytest.skip("longdouble has same precision as quad on this platform") - - # # Also need to consider that conversion goes through double - # # So precision is limited by double (~52 bit mantissa) - # double_info = np.finfo(np.float64) - - # # Values that exceed double precision - # failing_values = [ - # str(2**53 + 1), # First integer not exactly representable in double - # "1.0000000000000001", # 1 + small epsilon beyond double precision - # "3.141592653589793238462643383279502884197", # Pi with more than double precision - # ] - - # for val in failing_values: - # src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) - # with pytest.raises(ValueError): - # src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") - - - # @pytest.mark.parametrize("backend", ["sleef", "longdouble"]) - # def test_quad_to_quad_same_backend_always_passes(self, backend): - # """Same backend conversion should always pass same_value.""" - # # Even high-precision values should pass when backend is the same - # values = [ - # "3.141592653589793238462643383279502884197", - # "2.718281828459045235360287471352662497757", - # str(2**113), # Large integer - # "1e4000", # Large exponent (within quad range) - # ] - - # for val in values: - # src = np.array([val], dtype=QuadPrecDType(backend=backend)) - # result = src.astype(QuadPrecDType(backend=backend), casting="same_value") - # # Should not raise, and value should be unchanged - # assert str(result[0]) == str(src[0]) + # Verify value is preserved + if val == "nan": + assert np.isnan(result[0]) + else: + assert float(result[0]) == float(src[0]), f"Value {val} failed for {src_backend} -> {dst_backend}" + + + @pytest.mark.parametrize("src_backend,dst_backend", [ + ("sleef", "longdouble"), + ("longdouble", "sleef"), + ]) + def test_quad_to_quad_interbackend_same_value_casting_failing(self, src_backend, dst_backend): + """Test values that cannot roundtrip exactly between backends. + + Inter-backend conversion goes through double, so values exceeding + double's precision (~53 bits mantissa) will fail same_value casting. + """ + ld_info = np.finfo(np.longdouble) + double_info = np.finfo(np.float64) + + # Skip if longdouble has same precision as quad (PowerPC binary128) + # In that case, sleef <-> longdouble might use a direct path + if ld_info.nmant >= 112: + pytest.skip("longdouble has same precision as quad on this platform") + + # For longdouble -> sleef: only fails if longdouble has more precision than double + if src_backend == "longdouble" and ld_info.nmant <= double_info.nmant: + pytest.skip("longdouble has same or less precision than double on this platform") + + # Values that exceed double precision (53-bit mantissa) + # These will lose precision when going through the double conversion + failing_values = [ + str(2**53 + 1), # First integer not exactly representable in double + "3.141592653589793238462643383279502884197", # Pi with more than double precision + "1.00000000000000011", # 1 + epsilon beyond double precision + ] + + for val in failing_values: + src = np.array([val], dtype=QuadPrecDType(backend=src_backend)) + with pytest.raises(ValueError): + src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") + + + @pytest.mark.parametrize("backend", ["sleef", "longdouble"]) + def test_quad_to_quad_same_backend_always_passes(self, backend): + """Same backend conversion should always pass same_value.""" + # Even high-precision values should pass when backend is the same + values = [ + "3.141592653589793238462643383279502884197", + "2.718281828459045235360287471352662497757", + str(2**113), # Large integer + "1e4000", # Large exponent (within quad range) + ] + + for val in values: + src = np.array([val], dtype=QuadPrecDType(backend=backend)) + result = src.astype(QuadPrecDType(backend=backend), casting="same_value") + # Should not raise, and value should be unchanged + assert str(result[0]) == str(src[0]) \ No newline at end of file From e68e6be0b893c4f9fc00a2cf1a6bb3b2e83826bf Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Fri, 26 Dec 2025 14:48:26 +0530 Subject: [PATCH 26/41] nolong need pyucs path --- quaddtype/numpy_quaddtype/src/casts.cpp | 33 ------------------------- 1 file changed, 33 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 42eb63c3..bd0ec329 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -380,39 +380,6 @@ quad_to_unicode_resolve_descriptors(PyObject *NPY_UNUSED(self), PyArray_DTypeMet return static_cast(NPY_SAME_KIND_CASTING | NPY_SAME_VALUE_CASTING_FLAG); } -// Helper function: Convert quad to string with adaptive notation -static inline PyObject * -quad_to_string_adaptive(Sleef_quad *sleef_val, npy_intp unicode_size_chars) -{ - // Try positional format first to see if it would fit - PyObject *positional_str = Dragon4_Positional_QuadDType( - sleef_val, DigitMode_Unique, CutoffMode_TotalLength, SLEEF_QUAD_DECIMAL_DIG, 0, 1, - TrimMode_LeaveOneZero, 1, 0); - - if (positional_str == NULL) { - return NULL; - } - - const char *pos_str = PyUnicode_AsUTF8(positional_str); - if (pos_str == NULL) { - Py_DECREF(positional_str); - return NULL; - } - - // no need to scan full, only checking if its longer - npy_intp pos_len = strnlen(pos_str, unicode_size_chars + 1); - - // If positional format fits, use it; otherwise use scientific notation - if (pos_len <= unicode_size_chars) { - return positional_str; // Keep the positional string - } - Py_DECREF(positional_str); - // Use scientific notation with full precision - return Dragon4_Scientific_QuadDType(sleef_val, DigitMode_Unique, - SLEEF_QUAD_DECIMAL_DIG, 0, 1, - TrimMode_LeaveOneZero, 1, 2); -} - static inline const char * quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars) { From 3f0cf90992fade09cb934fb7eb37e8495e027692 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Fri, 26 Dec 2025 14:59:34 +0530 Subject: [PATCH 27/41] remove unused apis --- quaddtype/numpy_quaddtype/src/casts.cpp | 16 ++-------------- quaddtype/tests/test_quaddtype.py | 1 + 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index bd0ec329..83a91171 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -71,6 +71,8 @@ quad_to_quad_resolve_descriptors(PyObject *NPY_UNUSED(self), } // Helper function for quad-to-quad same_value check (inter-backend) +// NOTE: the inter-backend uses `double` as intermediate, +// so only values that can be exactly represented in double can pass same_value check static inline int quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_in, const quad_value *out_val, QuadBackendType backend_out) @@ -413,20 +415,6 @@ quad_to_string_adaptive_cstr(Sleef_quad *sleef_val, npy_intp unicode_size_chars) } -/* - * Same-value check for quad to string conversions. - * Works for bytes (S), unicode (U), and StringDType. - * - * Performs a roundtrip: quad -> string -> quad and verifies the value is preserved. - * The check uses the truncated string (up to str_len chars) to ensure the output - * buffer can faithfully represent the original value. - * - * @param in_val The input quad value - * @param str_buf The string representation (may be longer than str_len) - * @param str_len Length of the string that will actually be stored (excluding null terminator) - * @param backend The quad backend type - * @return 1 if same_value check passes, -1 if it fails (sets Python error) - */ static inline int quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, npy_intp str_len, QuadBackendType backend) diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 76fbe0c2..7be80ebb 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5604,6 +5604,7 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend) if val == "nan": assert np.isnan(result[0]) else: + # compare them as float, as these values anyhow have to under double's range to work assert float(result[0]) == float(src[0]), f"Value {val} failed for {src_backend} -> {dst_backend}" From 661312531ef0310b23db27b61e5db5fcaca1ce16 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Sat, 27 Dec 2025 14:21:48 +0530 Subject: [PATCH 28/41] fixing -0.0 casting --- quaddtype/numpy_quaddtype/src/casts.cpp | 66 +++++++++----------- quaddtype/numpy_quaddtype/src/constants.hpp | 1 + quaddtype/numpy_quaddtype/src/ops.hpp | 15 ++++- quaddtype/numpy_quaddtype/src/scalar.c | 1 - quaddtype/numpy_quaddtype/src/scalar_ops.cpp | 2 +- quaddtype/tests/test_quaddtype.py | 18 ++++-- 6 files changed, 57 insertions(+), 46 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 83a91171..3ae41d71 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -95,25 +95,23 @@ quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_ memcpy(&roundtrip.sleef_value, &temp, sizeof(Sleef_quad)); } - // Compare in SLEEF domain - if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value)) + // Compare in SLEEF domain && signbit preserved + bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value)); + if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; // Both NaN - if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value)) + if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; // Equal - if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) - return 1; // Both zeros } else { // Input was longdouble, output is SLEEF // Convert SLEEF back to longdouble for comparison - roundtrip.longdouble_value = static_cast(Sleef_cast_to_doubleq1(out_val->sleef_value)); + roundtrip.longdouble_value = static_cast(cast_sleef_to_double(out_val->sleef_value)); - // Compare in longdouble domain - if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) + // Compare in longdouble domain && signbit preserved + bool is_sign_preserved = (ld_signbit(&in_val->longdouble_value) == ld_signbit(&roundtrip.longdouble_value)); + if ((std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved) return 1; - if (in_val->longdouble_value == roundtrip.longdouble_value) - return 1; - if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + if ((in_val->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved) return 1; } @@ -158,7 +156,7 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], quad_value out_val; if (backend_in == BACKEND_SLEEF) { - out_val.longdouble_value = static_cast(Sleef_cast_to_doubleq1(in_val.sleef_value)); + out_val.longdouble_value = static_cast(cast_sleef_to_double(in_val.sleef_value)); } else { @@ -441,25 +439,20 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n } free(truncated_str); - // Compare original and roundtripped values + // Compare original and roundtripped values along with signbit if (backend == BACKEND_SLEEF) { // NaN == NaN for same_value purposes - if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value)) - return 1; - if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value)) + bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value)); + if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; - // Handle -0.0 == +0.0 case - if (Sleef_icmpeqq1(in_val->sleef_value, QUAD_ZERO) && - Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; } else { - if (std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) - return 1; - if (in_val->longdouble_value == roundtrip.longdouble_value) + bool is_sign_preserved = (ld_signbit(&in_val->longdouble_value) == ld_signbit(&roundtrip.longdouble_value)); + if ((std::isnan(in_val->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved) return 1; - // Handle -0.0 == +0.0 case - if (in_val->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + if ((in_val->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved) return 1; } @@ -1316,7 +1309,7 @@ inline npy_half from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - double d = Sleef_cast_to_doubleq1(x->sleef_value); + double d = cast_sleef_to_double(x->sleef_value); return npy_double_to_half(d); } else { @@ -1329,7 +1322,7 @@ inline float from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (float)Sleef_cast_to_doubleq1(x->sleef_value); + return (float)cast_sleef_to_double(x->sleef_value); } else { return (float)x->longdouble_value; @@ -1341,7 +1334,7 @@ inline double from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return Sleef_cast_to_doubleq1(x->sleef_value); + return cast_sleef_to_double(x->sleef_value); } else { return (double)x->longdouble_value; @@ -1353,7 +1346,7 @@ inline long double from_quad(const quad_value *x, QuadBackendType backend) { if (backend == BACKEND_SLEEF) { - return (long double)Sleef_cast_to_doubleq1(x->sleef_value); + return (long double)cast_sleef_to_double(x->sleef_value); } else { return x->longdouble_value; @@ -1367,23 +1360,20 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value)) + bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); + if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; - if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value)) - return 1; - // Handle -0.0 == +0.0 case: both zeros are considered equal for same_value casting - if(Sleef_icmpeqq1(x->sleef_value, QUAD_ZERO) && Sleef_icmpeqq1(roundtrip.sleef_value, QUAD_ZERO)) + if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; } else { - if(std::isnan(x->longdouble_value) && std::isnan(roundtrip.longdouble_value)) - return 1; - if(x->longdouble_value == roundtrip.longdouble_value) + bool is_sign_preserved = (ld_signbit(&x->longdouble_value) == ld_signbit(&roundtrip.longdouble_value)); + if((std::isnan(x->longdouble_value) && std::isnan(roundtrip.longdouble_value)) && is_sign_preserved) return 1; - // Handle -0.0 == +0.0 case for longdouble backend - if(x->longdouble_value == 0.0L && roundtrip.longdouble_value == 0.0L) + if((x->longdouble_value == roundtrip.longdouble_value) && is_sign_preserved) return 1; + } Sleef_quad sleef_val = quad_to_sleef_quad(x, backend); const char *val_str = quad_to_string_adaptive_cstr(&sleef_val, QUAD_STR_WIDTH); diff --git a/quaddtype/numpy_quaddtype/src/constants.hpp b/quaddtype/numpy_quaddtype/src/constants.hpp index 25bcabe9..4bafef38 100644 --- a/quaddtype/numpy_quaddtype/src/constants.hpp +++ b/quaddtype/numpy_quaddtype/src/constants.hpp @@ -12,6 +12,7 @@ extern "C" { // Quad precision constants using sleef_q macro #define QUAD_PRECISION_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) +#define QUAD_PRECISION_NEG_ZERO sleef_q(-0x0000000000000LL, 0x0000000000000000ULL, -16383) #define QUAD_PRECISION_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0) #define QUAD_PRECISION_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384) #define QUAD_PRECISION_NINF sleef_q(-0x1000000000000LL, 0x0000000000000000ULL, 16384) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index df9c4dd5..1503c994 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1,6 +1,7 @@ #include #include #include +#include "constants.hpp" // Quad Constants, generated with qutil #define QUAD_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) @@ -1657,4 +1658,16 @@ static inline npy_bool ld_logical_not(const long double *a) { return !ld_is_nonzero(a); -} \ No newline at end of file +} + + +// Casting operations +static inline double +cast_sleef_to_double(const Sleef_quad in) +{ + if (Sleef_icmpeqq1(in, QUAD_ZERO)) + { + return quad_signbit(&in) ? -0.0 : 0.0; + } + return Sleef_cast_to_doubleq1(in); +} diff --git a/quaddtype/numpy_quaddtype/src/scalar.c b/quaddtype/numpy_quaddtype/src/scalar.c index 8a63fd00..f1bcddd4 100644 --- a/quaddtype/numpy_quaddtype/src/scalar.c +++ b/quaddtype/numpy_quaddtype/src/scalar.c @@ -104,7 +104,6 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend) } double dval = PyFloat_AsDouble(py_float); Py_DECREF(py_float); - if (backend == BACKEND_SLEEF) { self->value.sleef_value = Sleef_cast_from_doubleq1(dval); } diff --git a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp index e5c4bdf4..b378f65e 100644 --- a/quaddtype/numpy_quaddtype/src/scalar_ops.cpp +++ b/quaddtype/numpy_quaddtype/src/scalar_ops.cpp @@ -213,7 +213,7 @@ static PyObject * QuadPrecision_float(QuadPrecisionObject *self) { if (self->backend == BACKEND_SLEEF) { - return PyFloat_FromDouble(Sleef_cast_to_doubleq1(self->value.sleef_value)); + return PyFloat_FromDouble(cast_sleef_to_double(self->value.sleef_value)); } else { return PyFloat_FromDouble((double)self->value.longdouble_value); diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index 7be80ebb..b302405c 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5454,11 +5454,13 @@ def test_same_value_cast_quad_to_int(self, dtype, passing, failing): @pytest.mark.parametrize("dtype", [ np.float16, np.float32, np.float64, np.longdouble ]) - @pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan')]) + @pytest.mark.parametrize("val", [0.0, -0.0, float('inf'), float('-inf'), float('nan'), float("-nan")]) def test_same_value_cast_floats_special_values(self, dtype, val): """Test that special floating-point values roundtrip correctly.""" q = np.array([val], dtype=QuadPrecDType()) result = q.astype(dtype, casting="same_value") + if str(val).startswith("-"): + assert np.signbit(result), f"Sign bit failed for {dtype} with value {val}" if np.isnan(val): assert np.isnan(result), f"NaN failed for {dtype}" else: @@ -5539,7 +5541,7 @@ def test_same_value_cast_strings_enough_width(self, dtype): values = [ "0.0", "-0.0", "1.0", "-1.0", "3.14159265358979323846264338327950288", # pi with full quad precision - "inf", "-inf", "nan", + "inf", "-inf", "nan", "-nan", "1.23e100", "-4.56e-100", ] @@ -5548,6 +5550,8 @@ def test_same_value_cast_strings_enough_width(self, dtype): result = q.astype(dtype, casting="same_value") # Convert back and verify back = result.astype(QuadPrecDType()) + if str(val).startswith("-"): + assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" if np.isnan(q[0]): assert np.isnan(back[0]), f"NaN roundtrip failed for {dtype}" else: @@ -5557,11 +5561,13 @@ def test_same_value_cast_strings_enough_width(self, dtype): def test_same_value_cast_strings_narrow_width(self, dtype): """Test that string types with narrow width fail for values that need more precision.""" # Values that can fit in 10 chars should pass - passing_values = ["0.0", "1.0", "-1.0", "inf", "-inf", "nan"] + passing_values = ["0.0", "-0.0", "1.0", "-1.0", "inf", "-inf", "nan", "-nan"] for val in passing_values: q = np.array([val], dtype=QuadPrecDType()) result = q.astype(dtype, casting="same_value") back = result.astype(QuadPrecDType()) + if str(val).startswith("-"): + assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" if np.isnan(q[0]): assert np.isnan(back[0]) else: @@ -5591,7 +5597,7 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend) 0.0, -0.0, 1.0, -1.0, 0.5, 0.25, 0.125, 2.0, 4.0, 8.0, - "inf", "-inf", "nan", + "inf", "-inf", "nan", "-nan", 1e100, -1e-100, str(2**52), # Largest consecutive integer in double ] @@ -5601,7 +5607,9 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend) result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") # Verify value is preserved - if val == "nan": + if str(val).startswith("-"): + assert np.signbit(result[0]), f"Sign bit failed for {val} in {src_backend} -> {dst_backend}" + if val in ["nan", "-nan"] : assert np.isnan(result[0]) else: # compare them as float, as these values anyhow have to under double's range to work From ef07ab2d3c7b89bfc007d04f8ba9588d7295ebf9 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Sat, 27 Dec 2025 15:38:50 +0530 Subject: [PATCH 29/41] -nan supported --- quaddtype/numpy_quaddtype/src/casts.cpp | 6 +++--- quaddtype/numpy_quaddtype/src/constants.hpp | 1 + quaddtype/numpy_quaddtype/src/dragon4.c | 24 ++++++++++----------- quaddtype/numpy_quaddtype/src/scalar.c | 5 +++++ 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 3ae41d71..aab6f8f2 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -85,7 +85,8 @@ quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_ // Convert longdouble back to SLEEF for comparison long double ld = out_val->longdouble_value; if (std::isnan(ld)) { - roundtrip.sleef_value = QUAD_PRECISION_NAN; + // Preserve sign of NaN + roundtrip.sleef_value = (!ld_signbit(&ld)) ? QUAD_PRECISION_NAN : QUAD_PRECISION_NEG_NAN; } else if (std::isinf(ld)) { roundtrip.sleef_value = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; @@ -162,7 +163,7 @@ quad_to_quad_strided_loop(PyArrayMethod_Context *context, char *const data[], { long double ld = in_val.longdouble_value; if (std::isnan(ld)) { - out_val.sleef_value = QUAD_PRECISION_NAN; + out_val.sleef_value = (!ld_signbit(&ld)) ? QUAD_PRECISION_NAN : QUAD_PRECISION_NEG_NAN; } else if (std::isinf(ld)) { out_val.sleef_value = (ld > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; @@ -441,7 +442,6 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n // Compare original and roundtripped values along with signbit if (backend == BACKEND_SLEEF) { - // NaN == NaN for same_value purposes bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value)); if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; diff --git a/quaddtype/numpy_quaddtype/src/constants.hpp b/quaddtype/numpy_quaddtype/src/constants.hpp index 4bafef38..e9733f04 100644 --- a/quaddtype/numpy_quaddtype/src/constants.hpp +++ b/quaddtype/numpy_quaddtype/src/constants.hpp @@ -17,6 +17,7 @@ extern "C" { #define QUAD_PRECISION_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384) #define QUAD_PRECISION_NINF sleef_q(-0x1000000000000LL, 0x0000000000000000ULL, 16384) #define QUAD_PRECISION_NAN sleef_q(+0x1ffffffffffffLL, 0xffffffffffffffffULL, 16384) +#define QUAD_PRECISION_NEG_NAN sleef_q(-0x1ffffffffffffLL, 0xffffffffffffffffULL, 16384) // Additional constants #define QUAD_PRECISION_MAX_FINITE SLEEF_QUAD_MAX diff --git a/quaddtype/numpy_quaddtype/src/dragon4.c b/quaddtype/numpy_quaddtype/src/dragon4.c index ecf7b18f..f86f2697 100644 --- a/quaddtype/numpy_quaddtype/src/dragon4.c +++ b/quaddtype/numpy_quaddtype/src/dragon4.c @@ -974,22 +974,22 @@ PrintInfNan(char *buffer, npy_uint32 bufferSize, npy_uint64 mantissa, npy_uint32 DEBUG_ASSERT(bufferSize > 0); + /* Print sign for both inf and nan values */ + if (signbit == '+') { + if (pos < maxPrintLen - 1) { + buffer[pos++] = '+'; + } + } + else if (signbit == '-') { + if (pos < maxPrintLen - 1) { + buffer[pos++] = '-'; + } + } + /* Check for infinity */ if (mantissa == 0) { npy_uint32 printLen; - /* only print sign for inf values (though nan can have a sign set) */ - if (signbit == '+') { - if (pos < maxPrintLen - 1) { - buffer[pos++] = '+'; - } - } - else if (signbit == '-') { - if (pos < maxPrintLen - 1) { - buffer[pos++] = '-'; - } - } - /* copy and make sure the buffer is terminated */ printLen = (3 < maxPrintLen - pos) ? 3 : maxPrintLen - pos; memcpy(buffer + pos, "inf", printLen); diff --git a/quaddtype/numpy_quaddtype/src/scalar.c b/quaddtype/numpy_quaddtype/src/scalar.c index f1bcddd4..34d9695e 100644 --- a/quaddtype/numpy_quaddtype/src/scalar.c +++ b/quaddtype/numpy_quaddtype/src/scalar.c @@ -186,6 +186,11 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend) self->value.sleef_value = Sleef_cast_from_doubleq1(dval); } else { + // np.longdouble does not preserve sign of while nan construction` + // if (isnan(dval)) { + // self->value.longdouble_value = copysignl(NAN, dval); + // } + // else self->value.longdouble_value = (long double)dval; } } From 770c117b4c1af4a8e2b7941318406a2812e516b2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 13:13:19 +0000 Subject: [PATCH 30/41] weird bug --- .github/workflows/build_wheels.yml | 346 ++++++++++++------------ quaddtype/numpy_quaddtype/src/casts.cpp | 3 +- 2 files changed, 175 insertions(+), 174 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index a01cf81a..88dc7447 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -16,179 +16,179 @@ on: workflow_dispatch: jobs: - build_wheels_linux: - name: Build wheels on Linux - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ">=3.11.0" - - - name: Install cibuildwheel - run: pip install cibuildwheel==3.1.4 - - - name: Build wheels - env: - CIBW_BUILD: "cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp313t-manylinux_x86_64 cp314-manylinux_x86_64 cp314t-manylinux_x86_64" - CIBW_ENABLE: cpython-prerelease cpython-freethreading - CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - CIBW_BUILD_VERBOSITY: "3" - CIBW_BEFORE_ALL: | - yum update -y - yum install -y cmake gcc gcc-c++ make git pkgconfig - CIBW_BEFORE_BUILD: | - pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja - pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - CIBW_ENVIRONMENT: > - LDFLAGS="-fopenmp" - CIBW_REPAIR_WHEEL_COMMAND: | - auditwheel repair -w {dest_dir} --plat manylinux_2_28_x86_64 {wheel} - CIBW_TEST_COMMAND: | - pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - pip install --no-deps {wheel} - pip install pytest pytest-run-parallel - if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then - pytest --parallel-threads=10 --iterations=10 {project}/tests - else - pytest -s {project}/tests - fi - CIBW_TEST_EXTRAS: "test" - run: | - python -m cibuildwheel --output-dir wheelhouse - working-directory: ./quaddtype - - - uses: actions/upload-artifact@v6 - with: - path: ./quaddtype/wheelhouse/*.whl - name: wheels-linux - - build_wheels_macos: - name: Build wheels on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-14, macos-15, macos-15-intel] - - steps: - - uses: actions/checkout@v6 - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ">=3.11.0" - - - name: Install dependencies - run: | - packages="cmake libomp git" - echo "Remove preinstalled dependencies" - for pkg in $packages; do - brew uninstall --ignore-dependencies $pkg 2>/dev/null || true - done - brew cleanup - brew install $packages - - - name: Installing Python dependencies - run: | - pip install -U pip - pip install cibuildwheel==3.1.4 - pip install pytest-run-parallel - - - name: Build wheels - env: - CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" - CIBW_ENABLE: cpython-prerelease cpython-freethreading - # CIBW_ARCHS_MACOS: ${{ matrix.os == 'macos-13' && 'x86_64' || 'arm64' }} - CIBW_BUILD_VERBOSITY: "3" - CIBW_BEFORE_BUILD: | - pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja - pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - CIBW_ENVIRONMENT: > - MACOSX_DEPLOYMENT_TARGET="${{ matrix.os == 'macos-14' && '14.0' || '15.0' }}" - CIBW_REPAIR_WHEEL_COMMAND: > - delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} - CIBW_TEST_COMMAND: | - pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - pip install --no-deps {wheel} - pip install pytest pytest-run-parallel - if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then - pytest --parallel-threads=10 --iterations=10 {project}/tests - else - pytest -s {project}/tests - fi - CIBW_TEST_EXTRAS: "test" - run: | - python -m cibuildwheel --output-dir wheelhouse - working-directory: ./quaddtype - - - uses: actions/upload-artifact@v6 - with: - path: ./quaddtype/wheelhouse/*.whl - name: wheels-${{ matrix.os }} - - # disabling QBLAS optimization for windows due to incompatibility with MSVC - build_wheels_windows: - name: Build wheels on Windows - runs-on: windows-latest - strategy: - matrix: - architecture: [x64] - - steps: - - uses: actions/checkout@v6 - - - name: Setup MSVC - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: ${{ matrix.architecture }} - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: ">=3.11.0" - architecture: ${{ matrix.architecture }} - - - name: Install CMake - uses: lukka/get-cmake@latest - - - name: Install build dependencies - shell: bash -l {0} - run: | - pip install -U pip - pip install cibuildwheel==3.1.4 ninja meson meson-python numpy delvewheel pytest - - - name: Build wheels - env: - CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" - CIBW_ENABLE: cpython-prerelease cpython-freethreading - CIBW_ARCHS_WINDOWS: ${{ matrix.architecture == 'x86' && 'x86' || 'AMD64' }} - CIBW_BUILD_VERBOSITY: "3" - DISTUTILS_USE_SDK: "1" - MSSdk: "1" - CIBW_BEFORE_BUILD: >- - pip install meson meson-python ninja wheel && - pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - CIBW_ENVIRONMENT: > - CFLAGS="/DDISABLE_QUADBLAS $CFLAGS" - CXXFLAGS="/DDISABLE_QUADBLAS $CXXFLAGS" - CIBW_REPAIR_WHEEL_COMMAND: 'delvewheel repair -w {dest_dir} {wheel} --add-path C:\sleef\bin' - CIBW_TEST_COMMAND_WINDOWS: pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy && pip install --no-deps {wheel} && pip install pytest pytest-run-parallel && pytest -s {project}/tests - CIBW_TEST_EXTRAS: test - shell: pwsh - run: | - python -m cibuildwheel --output-dir wheelhouse - if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" } - working-directory: ./quaddtype - - - uses: actions/upload-artifact@v6 - with: - path: ./quaddtype/wheelhouse/*.whl - name: wheels-windows-${{ matrix.architecture }} + # build_wheels_linux: + # name: Build wheels on Linux + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v6 + + # - name: Set up Python + # uses: actions/setup-python@v6 + # with: + # python-version: ">=3.11.0" + + # - name: Install cibuildwheel + # run: pip install cibuildwheel==3.1.4 + + # - name: Build wheels + # env: + # CIBW_BUILD: "cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp313t-manylinux_x86_64 cp314-manylinux_x86_64 cp314t-manylinux_x86_64" + # CIBW_ENABLE: cpython-prerelease cpython-freethreading + # CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + # CIBW_BUILD_VERBOSITY: "3" + # CIBW_BEFORE_ALL: | + # yum update -y + # yum install -y cmake gcc gcc-c++ make git pkgconfig + # CIBW_BEFORE_BUILD: | + # pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja + # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + # CIBW_ENVIRONMENT: > + # LDFLAGS="-fopenmp" + # CIBW_REPAIR_WHEEL_COMMAND: | + # auditwheel repair -w {dest_dir} --plat manylinux_2_28_x86_64 {wheel} + # CIBW_TEST_COMMAND: | + # pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # pip install --no-deps {wheel} + # pip install pytest pytest-run-parallel + # if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then + # pytest --parallel-threads=10 --iterations=10 {project}/tests + # else + # pytest -s {project}/tests + # fi + # CIBW_TEST_EXTRAS: "test" + # run: | + # python -m cibuildwheel --output-dir wheelhouse + # working-directory: ./quaddtype + + # - uses: actions/upload-artifact@v6 + # with: + # path: ./quaddtype/wheelhouse/*.whl + # name: wheels-linux + + # build_wheels_macos: + # name: Build wheels on ${{ matrix.os }} + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [macos-14, macos-15, macos-15-intel] + + # steps: + # - uses: actions/checkout@v6 + + # - name: Set up Python + # uses: actions/setup-python@v6 + # with: + # python-version: ">=3.11.0" + + # - name: Install dependencies + # run: | + # packages="cmake libomp git" + # echo "Remove preinstalled dependencies" + # for pkg in $packages; do + # brew uninstall --ignore-dependencies $pkg 2>/dev/null || true + # done + # brew cleanup + # brew install $packages + + # - name: Installing Python dependencies + # run: | + # pip install -U pip + # pip install cibuildwheel==3.1.4 + # pip install pytest-run-parallel + + # - name: Build wheels + # env: + # CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" + # CIBW_ENABLE: cpython-prerelease cpython-freethreading + # # CIBW_ARCHS_MACOS: ${{ matrix.os == 'macos-13' && 'x86_64' || 'arm64' }} + # CIBW_BUILD_VERBOSITY: "3" + # CIBW_BEFORE_BUILD: | + # pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja + # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + # CIBW_ENVIRONMENT: > + # MACOSX_DEPLOYMENT_TARGET="${{ matrix.os == 'macos-14' && '14.0' || '15.0' }}" + # CIBW_REPAIR_WHEEL_COMMAND: > + # delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} + # CIBW_TEST_COMMAND: | + # pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # pip install --no-deps {wheel} + # pip install pytest pytest-run-parallel + # if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then + # pytest --parallel-threads=10 --iterations=10 {project}/tests + # else + # pytest -s {project}/tests + # fi + # CIBW_TEST_EXTRAS: "test" + # run: | + # python -m cibuildwheel --output-dir wheelhouse + # working-directory: ./quaddtype + + # - uses: actions/upload-artifact@v6 + # with: + # path: ./quaddtype/wheelhouse/*.whl + # name: wheels-${{ matrix.os }} + + # # disabling QBLAS optimization for windows due to incompatibility with MSVC + # build_wheels_windows: + # name: Build wheels on Windows + # runs-on: windows-latest + # strategy: + # matrix: + # architecture: [x64] + + # steps: + # - uses: actions/checkout@v6 + + # - name: Setup MSVC + # uses: ilammy/msvc-dev-cmd@v1 + # with: + # arch: ${{ matrix.architecture }} + + # - name: Set up Python + # uses: actions/setup-python@v6 + # with: + # python-version: ">=3.11.0" + # architecture: ${{ matrix.architecture }} + + # - name: Install CMake + # uses: lukka/get-cmake@latest + + # - name: Install build dependencies + # shell: bash -l {0} + # run: | + # pip install -U pip + # pip install cibuildwheel==3.1.4 ninja meson meson-python numpy delvewheel pytest + + # - name: Build wheels + # env: + # CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" + # CIBW_ENABLE: cpython-prerelease cpython-freethreading + # CIBW_ARCHS_WINDOWS: ${{ matrix.architecture == 'x86' && 'x86' || 'AMD64' }} + # CIBW_BUILD_VERBOSITY: "3" + # DISTUTILS_USE_SDK: "1" + # MSSdk: "1" + # CIBW_BEFORE_BUILD: >- + # pip install meson meson-python ninja wheel && + # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + # CIBW_ENVIRONMENT: > + # CFLAGS="/DDISABLE_QUADBLAS $CFLAGS" + # CXXFLAGS="/DDISABLE_QUADBLAS $CXXFLAGS" + # CIBW_REPAIR_WHEEL_COMMAND: 'delvewheel repair -w {dest_dir} {wheel} --add-path C:\sleef\bin' + # CIBW_TEST_COMMAND_WINDOWS: pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy && pip install --no-deps {wheel} && pip install pytest pytest-run-parallel && pytest -s {project}/tests + # CIBW_TEST_EXTRAS: test + # shell: pwsh + # run: | + # python -m cibuildwheel --output-dir wheelhouse + # if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" } + # working-directory: ./quaddtype + + # - uses: actions/upload-artifact@v6 + # with: + # path: ./quaddtype/wheelhouse/*.whl + # name: wheels-windows-${{ matrix.architecture }} build_sdist: name: Build SDist diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index aab6f8f2..10300745 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1361,7 +1361,8 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken if(backend == BACKEND_SLEEF) { bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); - if(Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved) + // check if input is NaN and roundtrip is NaN with same sign + if(quad_isnan(&x->sleef_value) && quad_isnan(&roundtrip.sleef_value) && is_sign_preserved) return 1; if(Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; From 4199b57b890c7ac9be47e1b398568c8cfc0172a5 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 13:21:56 +0000 Subject: [PATCH 31/41] conjunction nan --- .github/workflows/build_wheels.yml | 346 ++++++++++++------------ quaddtype/numpy_quaddtype/src/casts.cpp | 4 +- 2 files changed, 175 insertions(+), 175 deletions(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 88dc7447..a01cf81a 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -16,179 +16,179 @@ on: workflow_dispatch: jobs: - # build_wheels_linux: - # name: Build wheels on Linux - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v6 - - # - name: Set up Python - # uses: actions/setup-python@v6 - # with: - # python-version: ">=3.11.0" - - # - name: Install cibuildwheel - # run: pip install cibuildwheel==3.1.4 - - # - name: Build wheels - # env: - # CIBW_BUILD: "cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp313t-manylinux_x86_64 cp314-manylinux_x86_64 cp314t-manylinux_x86_64" - # CIBW_ENABLE: cpython-prerelease cpython-freethreading - # CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 - # CIBW_BUILD_VERBOSITY: "3" - # CIBW_BEFORE_ALL: | - # yum update -y - # yum install -y cmake gcc gcc-c++ make git pkgconfig - # CIBW_BEFORE_BUILD: | - # pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja - # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - # CIBW_ENVIRONMENT: > - # LDFLAGS="-fopenmp" - # CIBW_REPAIR_WHEEL_COMMAND: | - # auditwheel repair -w {dest_dir} --plat manylinux_2_28_x86_64 {wheel} - # CIBW_TEST_COMMAND: | - # pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # pip install --no-deps {wheel} - # pip install pytest pytest-run-parallel - # if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then - # pytest --parallel-threads=10 --iterations=10 {project}/tests - # else - # pytest -s {project}/tests - # fi - # CIBW_TEST_EXTRAS: "test" - # run: | - # python -m cibuildwheel --output-dir wheelhouse - # working-directory: ./quaddtype - - # - uses: actions/upload-artifact@v6 - # with: - # path: ./quaddtype/wheelhouse/*.whl - # name: wheels-linux - - # build_wheels_macos: - # name: Build wheels on ${{ matrix.os }} - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [macos-14, macos-15, macos-15-intel] - - # steps: - # - uses: actions/checkout@v6 - - # - name: Set up Python - # uses: actions/setup-python@v6 - # with: - # python-version: ">=3.11.0" - - # - name: Install dependencies - # run: | - # packages="cmake libomp git" - # echo "Remove preinstalled dependencies" - # for pkg in $packages; do - # brew uninstall --ignore-dependencies $pkg 2>/dev/null || true - # done - # brew cleanup - # brew install $packages - - # - name: Installing Python dependencies - # run: | - # pip install -U pip - # pip install cibuildwheel==3.1.4 - # pip install pytest-run-parallel - - # - name: Build wheels - # env: - # CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" - # CIBW_ENABLE: cpython-prerelease cpython-freethreading - # # CIBW_ARCHS_MACOS: ${{ matrix.os == 'macos-13' && 'x86_64' || 'arm64' }} - # CIBW_BUILD_VERBOSITY: "3" - # CIBW_BEFORE_BUILD: | - # pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja - # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - # CIBW_ENVIRONMENT: > - # MACOSX_DEPLOYMENT_TARGET="${{ matrix.os == 'macos-14' && '14.0' || '15.0' }}" - # CIBW_REPAIR_WHEEL_COMMAND: > - # delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} - # CIBW_TEST_COMMAND: | - # pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # pip install --no-deps {wheel} - # pip install pytest pytest-run-parallel - # if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then - # pytest --parallel-threads=10 --iterations=10 {project}/tests - # else - # pytest -s {project}/tests - # fi - # CIBW_TEST_EXTRAS: "test" - # run: | - # python -m cibuildwheel --output-dir wheelhouse - # working-directory: ./quaddtype - - # - uses: actions/upload-artifact@v6 - # with: - # path: ./quaddtype/wheelhouse/*.whl - # name: wheels-${{ matrix.os }} - - # # disabling QBLAS optimization for windows due to incompatibility with MSVC - # build_wheels_windows: - # name: Build wheels on Windows - # runs-on: windows-latest - # strategy: - # matrix: - # architecture: [x64] - - # steps: - # - uses: actions/checkout@v6 - - # - name: Setup MSVC - # uses: ilammy/msvc-dev-cmd@v1 - # with: - # arch: ${{ matrix.architecture }} - - # - name: Set up Python - # uses: actions/setup-python@v6 - # with: - # python-version: ">=3.11.0" - # architecture: ${{ matrix.architecture }} - - # - name: Install CMake - # uses: lukka/get-cmake@latest - - # - name: Install build dependencies - # shell: bash -l {0} - # run: | - # pip install -U pip - # pip install cibuildwheel==3.1.4 ninja meson meson-python numpy delvewheel pytest - - # - name: Build wheels - # env: - # CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" - # CIBW_ENABLE: cpython-prerelease cpython-freethreading - # CIBW_ARCHS_WINDOWS: ${{ matrix.architecture == 'x86' && 'x86' || 'AMD64' }} - # CIBW_BUILD_VERBOSITY: "3" - # DISTUTILS_USE_SDK: "1" - # MSSdk: "1" - # CIBW_BEFORE_BUILD: >- - # pip install meson meson-python ninja wheel && - # pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - # CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" - # CIBW_ENVIRONMENT: > - # CFLAGS="/DDISABLE_QUADBLAS $CFLAGS" - # CXXFLAGS="/DDISABLE_QUADBLAS $CXXFLAGS" - # CIBW_REPAIR_WHEEL_COMMAND: 'delvewheel repair -w {dest_dir} {wheel} --add-path C:\sleef\bin' - # CIBW_TEST_COMMAND_WINDOWS: pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy && pip install --no-deps {wheel} && pip install pytest pytest-run-parallel && pytest -s {project}/tests - # CIBW_TEST_EXTRAS: test - # shell: pwsh - # run: | - # python -m cibuildwheel --output-dir wheelhouse - # if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" } - # working-directory: ./quaddtype - - # - uses: actions/upload-artifact@v6 - # with: - # path: ./quaddtype/wheelhouse/*.whl - # name: wheels-windows-${{ matrix.architecture }} + build_wheels_linux: + name: Build wheels on Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ">=3.11.0" + + - name: Install cibuildwheel + run: pip install cibuildwheel==3.1.4 + + - name: Build wheels + env: + CIBW_BUILD: "cp311-manylinux_x86_64 cp312-manylinux_x86_64 cp313-manylinux_x86_64 cp313t-manylinux_x86_64 cp314-manylinux_x86_64 cp314t-manylinux_x86_64" + CIBW_ENABLE: cpython-prerelease cpython-freethreading + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_BUILD_VERBOSITY: "3" + CIBW_BEFORE_ALL: | + yum update -y + yum install -y cmake gcc gcc-c++ make git pkgconfig + CIBW_BEFORE_BUILD: | + pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja + pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + CIBW_ENVIRONMENT: > + LDFLAGS="-fopenmp" + CIBW_REPAIR_WHEEL_COMMAND: | + auditwheel repair -w {dest_dir} --plat manylinux_2_28_x86_64 {wheel} + CIBW_TEST_COMMAND: | + pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + pip install --no-deps {wheel} + pip install pytest pytest-run-parallel + if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then + pytest --parallel-threads=10 --iterations=10 {project}/tests + else + pytest -s {project}/tests + fi + CIBW_TEST_EXTRAS: "test" + run: | + python -m cibuildwheel --output-dir wheelhouse + working-directory: ./quaddtype + + - uses: actions/upload-artifact@v6 + with: + path: ./quaddtype/wheelhouse/*.whl + name: wheels-linux + + build_wheels_macos: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-14, macos-15, macos-15-intel] + + steps: + - uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ">=3.11.0" + + - name: Install dependencies + run: | + packages="cmake libomp git" + echo "Remove preinstalled dependencies" + for pkg in $packages; do + brew uninstall --ignore-dependencies $pkg 2>/dev/null || true + done + brew cleanup + brew install $packages + + - name: Installing Python dependencies + run: | + pip install -U pip + pip install cibuildwheel==3.1.4 + pip install pytest-run-parallel + + - name: Build wheels + env: + CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" + CIBW_ENABLE: cpython-prerelease cpython-freethreading + # CIBW_ARCHS_MACOS: ${{ matrix.os == 'macos-13' && 'x86_64' || 'arm64' }} + CIBW_BUILD_VERBOSITY: "3" + CIBW_BEFORE_BUILD: | + pip install meson>=1.3.2 meson-python>=0.18.0 wheel ninja + pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + CIBW_ENVIRONMENT: > + MACOSX_DEPLOYMENT_TARGET="${{ matrix.os == 'macos-14' && '14.0' || '15.0' }}" + CIBW_REPAIR_WHEEL_COMMAND: > + delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} + CIBW_TEST_COMMAND: | + pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + pip install --no-deps {wheel} + pip install pytest pytest-run-parallel + if python -c "import sys; exit(0 if hasattr(sys, '_is_gil_enabled') and not sys._is_gil_enabled() else 1)"; then + pytest --parallel-threads=10 --iterations=10 {project}/tests + else + pytest -s {project}/tests + fi + CIBW_TEST_EXTRAS: "test" + run: | + python -m cibuildwheel --output-dir wheelhouse + working-directory: ./quaddtype + + - uses: actions/upload-artifact@v6 + with: + path: ./quaddtype/wheelhouse/*.whl + name: wheels-${{ matrix.os }} + + # disabling QBLAS optimization for windows due to incompatibility with MSVC + build_wheels_windows: + name: Build wheels on Windows + runs-on: windows-latest + strategy: + matrix: + architecture: [x64] + + steps: + - uses: actions/checkout@v6 + + - name: Setup MSVC + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: ${{ matrix.architecture }} + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ">=3.11.0" + architecture: ${{ matrix.architecture }} + + - name: Install CMake + uses: lukka/get-cmake@latest + + - name: Install build dependencies + shell: bash -l {0} + run: | + pip install -U pip + pip install cibuildwheel==3.1.4 ninja meson meson-python numpy delvewheel pytest + + - name: Build wheels + env: + CIBW_BUILD: "cp311-* cp312-* cp313-* cp314-* cp313t-* cp314t-*" + CIBW_ENABLE: cpython-prerelease cpython-freethreading + CIBW_ARCHS_WINDOWS: ${{ matrix.architecture == 'x86' && 'x86' || 'AMD64' }} + CIBW_BUILD_VERBOSITY: "3" + DISTUTILS_USE_SDK: "1" + MSSdk: "1" + CIBW_BEFORE_BUILD: >- + pip install meson meson-python ninja wheel && + pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy + CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation" + CIBW_ENVIRONMENT: > + CFLAGS="/DDISABLE_QUADBLAS $CFLAGS" + CXXFLAGS="/DDISABLE_QUADBLAS $CXXFLAGS" + CIBW_REPAIR_WHEEL_COMMAND: 'delvewheel repair -w {dest_dir} {wheel} --add-path C:\sleef\bin' + CIBW_TEST_COMMAND_WINDOWS: pip install --pre --upgrade --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy && pip install --no-deps {wheel} && pip install pytest pytest-run-parallel && pytest -s {project}/tests + CIBW_TEST_EXTRAS: test + shell: pwsh + run: | + python -m cibuildwheel --output-dir wheelhouse + if (-not (Test-Path wheelhouse/*.whl)) { throw "Wheel was not created" } + working-directory: ./quaddtype + + - uses: actions/upload-artifact@v6 + with: + path: ./quaddtype/wheelhouse/*.whl + name: wheels-windows-${{ matrix.architecture }} build_sdist: name: Build SDist diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 10300745..9c5551cd 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -98,7 +98,7 @@ quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_ // Compare in SLEEF domain && signbit preserved bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value)); - if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) + if(quad_isnan(&in_val->sleef_value) && quad_isnan(&roundtrip.sleef_value) && is_sign_preserved) return 1; // Both NaN if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; // Equal @@ -443,7 +443,7 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n // Compare original and roundtripped values along with signbit if (backend == BACKEND_SLEEF) { bool is_sign_preserved = (quad_signbit(&in_val->sleef_value) == quad_signbit(&roundtrip.sleef_value)); - if (Sleef_iunordq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) + if(quad_isnan(&in_val->sleef_value) && quad_isnan(&roundtrip.sleef_value) && is_sign_preserved) return 1; if (Sleef_icmpeqq1(in_val->sleef_value, roundtrip.sleef_value) && is_sign_preserved) return 1; From 4256e126235071e31e4b65d666c15b88135dc407 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 13:41:13 +0000 Subject: [PATCH 32/41] fixing to_quad --- quaddtype/numpy_quaddtype/src/casts.cpp | 42 +++++++++-- quaddtype/numpy_quaddtype/src/ops.hpp | 99 ++++++++++++------------- 2 files changed, 84 insertions(+), 57 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 9c5551cd..027a8633 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1079,8 +1079,18 @@ inline quad_value to_quad(float x, QuadBackendType backend) { quad_value result; - if (backend == BACKEND_SLEEF) { - result.sleef_value = Sleef_cast_from_doubleq1(x); + if (backend == BACKEND_SLEEF) + { + if (std::isnan(x)) { + result.sleef_value = std::signbit(x) ? QUAD_PRECISION_NEG_NAN : QUAD_PRECISION_NAN; + } + else if (std::isinf(x)) { + result.sleef_value = (x > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else { + Sleef_quad temp = Sleef_cast_from_doubleq1(static_cast(x)); + std::memcpy(&result.sleef_value, &temp, sizeof(Sleef_quad)); + } } else { result.longdouble_value = (long double)x; @@ -1093,8 +1103,18 @@ inline quad_value to_quad(double x, QuadBackendType backend) { quad_value result; - if (backend == BACKEND_SLEEF) { - result.sleef_value = Sleef_cast_from_doubleq1(x); + if (backend == BACKEND_SLEEF) + { + if (std::isnan(x)) { + result.sleef_value = std::signbit(x) ? QUAD_PRECISION_NEG_NAN : QUAD_PRECISION_NAN; + } + else if (std::isinf(x)) { + result.sleef_value = (x > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else { + Sleef_quad temp = Sleef_cast_from_doubleq1(x); + std::memcpy(&result.sleef_value, &temp, sizeof(Sleef_quad)); + } } else { result.longdouble_value = (long double)x; @@ -1107,8 +1127,18 @@ inline quad_value to_quad(long double x, QuadBackendType backend) { quad_value result; - if (backend == BACKEND_SLEEF) { - result.sleef_value = Sleef_cast_from_doubleq1(x); + if (backend == BACKEND_SLEEF) + { + if (std::isnan(x)) { + result.sleef_value = std::signbit(x) ? QUAD_PRECISION_NEG_NAN : QUAD_PRECISION_NAN; + } + else if (std::isinf(x)) { + result.sleef_value = (x > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else { + Sleef_quad temp = Sleef_cast_from_doubleq1(static_cast(x)); + std::memcpy(&result.sleef_value, &temp, sizeof(Sleef_quad)); + } } else { result.longdouble_value = x; diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 1503c994..31f13b5b 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -4,10 +4,7 @@ #include "constants.hpp" // Quad Constants, generated with qutil -#define QUAD_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) #define QUAD_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0) -#define QUAD_POS_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384) -#define QUAD_NAN sleef_q(+0x1ffffffffffffLL, 0xffffffffffffffffULL, 16384) // Unary Quad Operations typedef Sleef_quad (*unary_op_quad_def)(const Sleef_quad *); @@ -29,7 +26,7 @@ quad_positive(const Sleef_quad *op) static inline Sleef_quad quad_sign(const Sleef_quad *op) { - int sign = Sleef_icmpq1(*op, QUAD_ZERO); + int sign = Sleef_icmpq1(*op, QUAD_PRECISION_ZERO); // sign(x=NaN) = x; otherwise sign(x) in { -1.0; 0.0; +1.0 } return Sleef_iunordq1(*op, *op) ? *op : Sleef_cast_from_int64q1(sign); } @@ -97,11 +94,11 @@ quad_cbrt(const Sleef_quad *op) if (Sleef_iunordq1(*op, *op)) { return *op; // NaN } - if (Sleef_icmpeqq1(*op, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*op, QUAD_PRECISION_ZERO)) { return *op; // ±0 } // Check if op is ±inf: isinf(x) = abs(x) == inf - if (Sleef_icmpeqq1(Sleef_fabsq1(*op), QUAD_POS_INF)) { + if (Sleef_icmpeqq1(Sleef_fabsq1(*op), QUAD_PRECISION_INF)) { return *op; // ±inf } @@ -110,7 +107,7 @@ quad_cbrt(const Sleef_quad *op) Sleef_quad one_third = Sleef_divq1_u05(QUAD_ONE, three); // Handle negative values: cbrt(-x) = -cbrt(x) - if (Sleef_icmpltq1(*op, QUAD_ZERO)) { + if (Sleef_icmpltq1(*op, QUAD_PRECISION_ZERO)) { Sleef_quad abs_val = Sleef_fabsq1(*op); Sleef_quad result = Sleef_powq1_u10(abs_val, one_third); return Sleef_negq1(result); @@ -497,21 +494,21 @@ quad_signbit(const Sleef_quad *op) // once we test big and little endian in CI Sleef_quad one_signed = Sleef_copysignq1(QUAD_ONE, *op); // signbit(x) = 1 iff copysign(1, x) == -1 - return Sleef_icmpltq1(one_signed, QUAD_ZERO); + return Sleef_icmpltq1(one_signed, QUAD_PRECISION_ZERO); } static inline npy_bool quad_isfinite(const Sleef_quad *op) { // isfinite(x) = abs(x) < inf - return Sleef_icmpltq1(Sleef_fabsq1(*op), QUAD_POS_INF); + return Sleef_icmpltq1(Sleef_fabsq1(*op), QUAD_PRECISION_INF); } static inline npy_bool quad_isinf(const Sleef_quad *op) { // isinf(x) = abs(x) == inf - return Sleef_icmpeqq1(Sleef_fabsq1(*op), QUAD_POS_INF); + return Sleef_icmpeqq1(Sleef_fabsq1(*op), QUAD_PRECISION_INF); } static inline npy_bool @@ -586,13 +583,13 @@ quad_floor_divide(const Sleef_quad *a, const Sleef_quad *b) // inf / finite_nonzero or -inf / finite_nonzero -> NaN // But inf / 0 -> inf - if (quad_isinf(a) && quad_isfinite(b) && !Sleef_icmpeqq1(*b, QUAD_ZERO)) { - return QUAD_NAN; + if (quad_isinf(a) && quad_isfinite(b) && !Sleef_icmpeqq1(*b, QUAD_PRECISION_ZERO)) { + return QUAD_PRECISION_NAN; } // 0 / 0 (including -0.0 / 0.0, 0.0 / -0.0, -0.0 / -0.0) -> NaN - if (Sleef_icmpeqq1(*a, QUAD_ZERO) && Sleef_icmpeqq1(*b, QUAD_ZERO)) { - return QUAD_NAN; + if (Sleef_icmpeqq1(*a, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*b, QUAD_PRECISION_ZERO)) { + return QUAD_PRECISION_NAN; } Sleef_quad quotient = Sleef_divq1_u05(*a, *b); @@ -601,8 +598,8 @@ quad_floor_divide(const Sleef_quad *a, const Sleef_quad *b) // floor_divide semantics: when result is -0.0 from non-zero numerator, convert to -1.0 // This happens when: (negative & non-zero)/+inf, (positive & non-zero)/-inf // But NOT when numerator is ±0.0 (then result stays as ±0.0) - if (Sleef_icmpeqq1(result, QUAD_ZERO) && quad_signbit(&result) && - !Sleef_icmpeqq1(*a, QUAD_ZERO)) { + if (Sleef_icmpeqq1(result, QUAD_PRECISION_ZERO) && quad_signbit(&result) && + !Sleef_icmpeqq1(*a, QUAD_PRECISION_ZERO)) { return Sleef_negq1(QUAD_ONE); // -1.0 } @@ -619,8 +616,8 @@ static inline Sleef_quad quad_mod(const Sleef_quad *a, const Sleef_quad *b) { // division by zero - if (Sleef_icmpeqq1(*b, QUAD_ZERO)) { - return QUAD_NAN; + if (Sleef_icmpeqq1(*b, QUAD_PRECISION_ZERO)) { + return QUAD_PRECISION_NAN; } // NaN inputs @@ -630,7 +627,7 @@ quad_mod(const Sleef_quad *a, const Sleef_quad *b) // infinity dividend -> NaN if (quad_isinf(a)) { - return QUAD_NAN; + return QUAD_PRECISION_NAN; } // finite % inf @@ -650,12 +647,12 @@ quad_mod(const Sleef_quad *a, const Sleef_quad *b) // Handle zero result sign: when result is exactly zero, // it should have the same sign as the divisor (NumPy convention) - if (Sleef_icmpeqq1(result, QUAD_ZERO)) { - if (Sleef_icmpltq1(*b, QUAD_ZERO)) { - return Sleef_negq1(QUAD_ZERO); // -0.0 + if (Sleef_icmpeqq1(result, QUAD_PRECISION_ZERO)) { + if (Sleef_icmpltq1(*b, QUAD_PRECISION_ZERO)) { + return Sleef_negq1(QUAD_PRECISION_ZERO); // -0.0 } else { - return QUAD_ZERO; // +0.0 + return QUAD_PRECISION_ZERO; // +0.0 } } @@ -671,13 +668,13 @@ quad_fmod(const Sleef_quad *a, const Sleef_quad *b) } // Division by zero -> NaN - if (Sleef_icmpeqq1(*b, QUAD_ZERO)) { - return QUAD_NAN; + if (Sleef_icmpeqq1(*b, QUAD_PRECISION_ZERO)) { + return QUAD_PRECISION_NAN; } // Infinity dividend -> NaN if (quad_isinf(a)) { - return QUAD_NAN; + return QUAD_PRECISION_NAN; } // Finite % infinity -> return dividend (same as a) @@ -688,14 +685,14 @@ quad_fmod(const Sleef_quad *a, const Sleef_quad *b) // x - trunc(x/y) * y Sleef_quad result = Sleef_fmodq1(*a, *b); - if (Sleef_icmpeqq1(result, QUAD_ZERO)) { + if (Sleef_icmpeqq1(result, QUAD_PRECISION_ZERO)) { // Preserve sign of dividend (first argument) Sleef_quad sign_test = Sleef_copysignq1(QUAD_ONE, *a); - if (Sleef_icmpltq1(sign_test, QUAD_ZERO)) { - return Sleef_negq1(QUAD_ZERO); // -0.0 + if (Sleef_icmpltq1(sign_test, QUAD_PRECISION_ZERO)) { + return Sleef_negq1(QUAD_PRECISION_ZERO); // -0.0 } else { - return QUAD_ZERO; // +0.0 + return QUAD_PRECISION_ZERO; // +0.0 } } @@ -717,7 +714,7 @@ quad_minimum(const Sleef_quad *in1, const Sleef_quad *in2) return Sleef_iunordq1(*in1, *in1) ? *in1 : *in2; } // minimum(-0.0, +0.0) = -0.0 - if (Sleef_icmpeqq1(*in1, QUAD_ZERO) && Sleef_icmpeqq1(*in2, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { return Sleef_icmpleq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; } return Sleef_fminq1(*in1, *in2); @@ -730,7 +727,7 @@ quad_maximum(const Sleef_quad *in1, const Sleef_quad *in2) return Sleef_iunordq1(*in1, *in1) ? *in1 : *in2; } // maximum(-0.0, +0.0) = +0.0 - if (Sleef_icmpeqq1(*in1, QUAD_ZERO) && Sleef_icmpeqq1(*in2, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; } return Sleef_fmaxq1(*in1, *in2); @@ -743,7 +740,7 @@ quad_fmin(const Sleef_quad *in1, const Sleef_quad *in2) return Sleef_iunordq1(*in2, *in2) ? *in1 : *in2; } // fmin(-0.0, +0.0) = -0.0 - if (Sleef_icmpeqq1(*in1, QUAD_ZERO) && Sleef_icmpeqq1(*in2, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { return Sleef_icmpleq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; } return Sleef_fminq1(*in1, *in2); @@ -756,7 +753,7 @@ quad_fmax(const Sleef_quad *in1, const Sleef_quad *in2) return Sleef_iunordq1(*in2, *in2) ? *in1 : *in2; } // maximum(-0.0, +0.0) = +0.0 - if (Sleef_icmpeqq1(*in1, QUAD_ZERO) && Sleef_icmpeqq1(*in2, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; } return Sleef_fmaxq1(*in1, *in2); @@ -787,14 +784,14 @@ quad_logaddexp(const Sleef_quad *x, const Sleef_quad *y) // Handle infinities // If both are -inf, result is -inf - Sleef_quad neg_inf = Sleef_negq1(QUAD_POS_INF); + Sleef_quad neg_inf = Sleef_negq1(QUAD_PRECISION_INF); if (Sleef_icmpeqq1(*x, neg_inf) && Sleef_icmpeqq1(*y, neg_inf)) { return neg_inf; } // If either is +inf, result is +inf - if (Sleef_icmpeqq1(*x, QUAD_POS_INF) || Sleef_icmpeqq1(*y, QUAD_POS_INF)) { - return QUAD_POS_INF; + if (Sleef_icmpeqq1(*x, QUAD_PRECISION_INF) || Sleef_icmpeqq1(*y, QUAD_PRECISION_INF)) { + return QUAD_PRECISION_INF; } // If one is -inf, result is the other value @@ -829,14 +826,14 @@ quad_logaddexp2(const Sleef_quad *x, const Sleef_quad *y) // Handle infinities // If both are -inf, result is -inf - Sleef_quad neg_inf = Sleef_negq1(QUAD_POS_INF); + Sleef_quad neg_inf = Sleef_negq1(QUAD_PRECISION_INF); if (Sleef_icmpeqq1(*x, neg_inf) && Sleef_icmpeqq1(*y, neg_inf)) { return neg_inf; } // If either is +inf, result is +inf - if (Sleef_icmpeqq1(*x, QUAD_POS_INF) || Sleef_icmpeqq1(*y, QUAD_POS_INF)) { - return QUAD_POS_INF; + if (Sleef_icmpeqq1(*x, QUAD_PRECISION_INF) || Sleef_icmpeqq1(*y, QUAD_PRECISION_INF)) { + return QUAD_PRECISION_INF; } // If one is -inf, result is the other value @@ -868,10 +865,10 @@ quad_heaviside(const Sleef_quad *x1, const Sleef_quad *x2) return *x1; // x1 is NaN, return NaN } - if (Sleef_icmpltq1(*x1, QUAD_ZERO)) { - return QUAD_ZERO; + if (Sleef_icmpltq1(*x1, QUAD_PRECISION_ZERO)) { + return QUAD_PRECISION_ZERO; } - else if (Sleef_icmpeqq1(*x1, QUAD_ZERO)) { + else if (Sleef_icmpeqq1(*x1, QUAD_PRECISION_ZERO)) { return *x2; // When x1 == 0, return x2 (even if x2 is NaN) } else { @@ -1026,17 +1023,17 @@ quad_spacing(const Sleef_quad *x) // Handle infinity -> NaN (numpy convention) if (quad_isinf(x)) { - return QUAD_NAN; + return QUAD_PRECISION_NAN; } // Determine direction based on sign of x Sleef_quad direction; - if (Sleef_icmpltq1(*x, QUAD_ZERO)) { + if (Sleef_icmpltq1(*x, QUAD_PRECISION_ZERO)) { // Negative: move toward -inf - direction = Sleef_negq1(QUAD_POS_INF); + direction = Sleef_negq1(QUAD_PRECISION_INF); } else { // Positive or zero: move toward +inf - direction = QUAD_POS_INF; + direction = QUAD_PRECISION_INF; } // Compute nextafter(x, direction) @@ -1068,7 +1065,7 @@ quad_ldexp(const Sleef_quad *x, const int *exp) } // ±0 * 2^exp = ±0 (preserves sign of zero) - if (Sleef_icmpeqq1(*x, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*x, QUAD_PRECISION_ZERO)) { return *x; } @@ -1125,7 +1122,7 @@ quad_frexp(const Sleef_quad *x, int *exp) } // ±0 -> mantissa ±0 with exponent 0 (preserves sign of zero) - if (Sleef_icmpeqq1(*x, QUAD_ZERO)) { + if (Sleef_icmpeqq1(*x, QUAD_PRECISION_ZERO)) { *exp = 0; return *x; } @@ -1588,7 +1585,7 @@ quad_is_nonzero(const Sleef_quad *a) { // A value is falsy if it's exactly zero (positive or negative) // NaN and inf are truthy - npy_bool is_zero = Sleef_icmpeqq1(*a, QUAD_ZERO); + npy_bool is_zero = Sleef_icmpeqq1(*a, QUAD_PRECISION_ZERO); return !is_zero; } @@ -1665,7 +1662,7 @@ ld_logical_not(const long double *a) static inline double cast_sleef_to_double(const Sleef_quad in) { - if (Sleef_icmpeqq1(in, QUAD_ZERO)) + if (Sleef_icmpeqq1(in, QUAD_PRECISION_ZERO)) { return quad_signbit(&in) ? -0.0 : 0.0; } From e879e1f9c17dd1d0f5bb37ac114b8dbed885e2f2 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 13:56:24 +0000 Subject: [PATCH 33/41] adding debug --- quaddtype/numpy_quaddtype/src/casts.cpp | 70 +++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 027a8633..c6264748 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1065,11 +1065,21 @@ inline quad_value to_quad(npy_half x, QuadBackendType backend) { quad_value result; + double d = npy_half_to_double(x); if (backend == BACKEND_SLEEF) { - result.sleef_value = Sleef_cast_from_doubleq1(npy_half_to_double(x)); + if (std::isnan(d)) { + result.sleef_value = std::signbit(d) ? QUAD_PRECISION_NEG_NAN : QUAD_PRECISION_NAN; + } + else if (std::isinf(d)) { + result.sleef_value = (d > 0) ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; + } + else { + Sleef_quad temp = Sleef_cast_from_doubleq1(d); + std::memcpy(&result.sleef_value, &temp, sizeof(Sleef_quad)); + } } else { - result.longdouble_value = (long double)npy_half_to_double(x); + result.longdouble_value = (long double)d; } return result; } @@ -1390,7 +1400,61 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); + + // debug statements + union { + Sleef_quad q; + struct { + uint64_t lo; + uint64_t hi; + } bits; + } input_bits, roundtrip_bits, canonical_nan_bits, canonical_neg_nan_bits; + + input_bits.q = x->sleef_value; + roundtrip_bits.q = roundtrip.sleef_value; + canonical_nan_bits.q = QUAD_PRECISION_NAN; + canonical_neg_nan_bits.q = QUAD_PRECISION_NEG_NAN; + + int input_signbit = quad_signbit(&x->sleef_value); + int roundtrip_signbit = quad_signbit(&roundtrip.sleef_value); + int input_isnan = Sleef_iunordq1(x->sleef_value, x->sleef_value); + int roundtrip_isnan = Sleef_iunordq1(roundtrip.sleef_value, roundtrip.sleef_value); + int both_unord = Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value); + int are_equal = Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value); + bool is_sign_preserved = (input_signbit == roundtrip_signbit); + + if (input_isnan || roundtrip_isnan) { + fprintf(stderr, "\n=== DEBUG: NaN detected in same_value_check ===\n"); + fprintf(stderr, "Input bits: hi=0x%016llx lo=0x%016llx\n", + (unsigned long long)input_bits.bits.hi, + (unsigned long long)input_bits.bits.lo); + fprintf(stderr, "Roundtrip bits: hi=0x%016llx lo=0x%016llx\n", + (unsigned long long)roundtrip_bits.bits.hi, + (unsigned long long)roundtrip_bits.bits.lo); + fprintf(stderr, "QUAD_PRECISION_NAN: hi=0x%016llx lo=0x%016llx\n", + (unsigned long long)canonical_nan_bits.bits.hi, + (unsigned long long)canonical_nan_bits.bits.lo); + fprintf(stderr, "QUAD_PREC_NEG_NAN: hi=0x%016llx lo=0x%016llx\n", + (unsigned long long)canonical_neg_nan_bits.bits.hi, + (unsigned long long)canonical_neg_nan_bits.bits.lo); + fprintf(stderr, "input_signbit=%d, roundtrip_signbit=%d\n", + input_signbit, roundtrip_signbit); + fprintf(stderr, "input_isnan=%d, roundtrip_isnan=%d\n", + input_isnan, roundtrip_isnan); + fprintf(stderr, "both_unord=%d, are_equal=%d, is_sign_preserved=%d\n", + both_unord, are_equal, is_sign_preserved ? 1 : 0); + + // Also debug the intermediate value + fprintf(stderr, "Intermediate value y (as double): %.17g\n", (double)(*y)); + fprintf(stderr, "std::signbit(y): %d\n", std::signbit((double)(*y))); + fprintf(stderr, "std::isnan(y): %d\n", std::isnan((double)(*y))); + fprintf(stderr, "=== END DEBUG ===\n\n"); + fflush(stderr); + } + + // DONE + + // bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); // check if input is NaN and roundtrip is NaN with same sign if(quad_isnan(&x->sleef_value) && quad_isnan(&roundtrip.sleef_value) && is_sign_preserved) return 1; From a4f9359d97f7eac569d7ef24acbb1ce685a1eb6d Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 14:05:36 +0000 Subject: [PATCH 34/41] hadling special sleef->d --- quaddtype/numpy_quaddtype/src/ops.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 31f13b5b..55fc17fe 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -1662,6 +1662,12 @@ ld_logical_not(const long double *a) static inline double cast_sleef_to_double(const Sleef_quad in) { + if (quad_isnan(&in)) { + return quad_signbit(&in) ? -NAN : NAN; + } + if (quad_isinf(&in)) { + return quad_signbit(&in) ? -INFINITY : INFINITY; + } if (Sleef_icmpeqq1(in, QUAD_PRECISION_ZERO)) { return quad_signbit(&in) ? -0.0 : 0.0; From 61e537c8e8ab79bd722ea3f12e9477665683493e Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 14:16:03 +0000 Subject: [PATCH 35/41] remove debug stmt --- quaddtype/numpy_quaddtype/src/casts.cpp | 56 +------------------------ 1 file changed, 1 insertion(+), 55 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index c6264748..258760b0 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -1400,61 +1400,7 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken quad_value roundtrip = to_quad(*y, backend); if(backend == BACKEND_SLEEF) { - - // debug statements - union { - Sleef_quad q; - struct { - uint64_t lo; - uint64_t hi; - } bits; - } input_bits, roundtrip_bits, canonical_nan_bits, canonical_neg_nan_bits; - - input_bits.q = x->sleef_value; - roundtrip_bits.q = roundtrip.sleef_value; - canonical_nan_bits.q = QUAD_PRECISION_NAN; - canonical_neg_nan_bits.q = QUAD_PRECISION_NEG_NAN; - - int input_signbit = quad_signbit(&x->sleef_value); - int roundtrip_signbit = quad_signbit(&roundtrip.sleef_value); - int input_isnan = Sleef_iunordq1(x->sleef_value, x->sleef_value); - int roundtrip_isnan = Sleef_iunordq1(roundtrip.sleef_value, roundtrip.sleef_value); - int both_unord = Sleef_iunordq1(x->sleef_value, roundtrip.sleef_value); - int are_equal = Sleef_icmpeqq1(x->sleef_value, roundtrip.sleef_value); - bool is_sign_preserved = (input_signbit == roundtrip_signbit); - - if (input_isnan || roundtrip_isnan) { - fprintf(stderr, "\n=== DEBUG: NaN detected in same_value_check ===\n"); - fprintf(stderr, "Input bits: hi=0x%016llx lo=0x%016llx\n", - (unsigned long long)input_bits.bits.hi, - (unsigned long long)input_bits.bits.lo); - fprintf(stderr, "Roundtrip bits: hi=0x%016llx lo=0x%016llx\n", - (unsigned long long)roundtrip_bits.bits.hi, - (unsigned long long)roundtrip_bits.bits.lo); - fprintf(stderr, "QUAD_PRECISION_NAN: hi=0x%016llx lo=0x%016llx\n", - (unsigned long long)canonical_nan_bits.bits.hi, - (unsigned long long)canonical_nan_bits.bits.lo); - fprintf(stderr, "QUAD_PREC_NEG_NAN: hi=0x%016llx lo=0x%016llx\n", - (unsigned long long)canonical_neg_nan_bits.bits.hi, - (unsigned long long)canonical_neg_nan_bits.bits.lo); - fprintf(stderr, "input_signbit=%d, roundtrip_signbit=%d\n", - input_signbit, roundtrip_signbit); - fprintf(stderr, "input_isnan=%d, roundtrip_isnan=%d\n", - input_isnan, roundtrip_isnan); - fprintf(stderr, "both_unord=%d, are_equal=%d, is_sign_preserved=%d\n", - both_unord, are_equal, is_sign_preserved ? 1 : 0); - - // Also debug the intermediate value - fprintf(stderr, "Intermediate value y (as double): %.17g\n", (double)(*y)); - fprintf(stderr, "std::signbit(y): %d\n", std::signbit((double)(*y))); - fprintf(stderr, "std::isnan(y): %d\n", std::isnan((double)(*y))); - fprintf(stderr, "=== END DEBUG ===\n\n"); - fflush(stderr); - } - - // DONE - - // bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); + bool is_sign_preserved = (quad_signbit(&x->sleef_value) == quad_signbit(&roundtrip.sleef_value)); // check if input is NaN and roundtrip is NaN with same sign if(quad_isnan(&x->sleef_value) && quad_isnan(&roundtrip.sleef_value) && is_sign_preserved) return 1; From 4bca201bce0830fa4909081a3037ee01a513b9d3 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 14:28:13 +0000 Subject: [PATCH 36/41] ld handling not needed --- quaddtype/numpy_quaddtype/src/scalar.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/scalar.c b/quaddtype/numpy_quaddtype/src/scalar.c index 34d9695e..f1bcddd4 100644 --- a/quaddtype/numpy_quaddtype/src/scalar.c +++ b/quaddtype/numpy_quaddtype/src/scalar.c @@ -186,11 +186,6 @@ QuadPrecision_from_object(PyObject *value, QuadBackendType backend) self->value.sleef_value = Sleef_cast_from_doubleq1(dval); } else { - // np.longdouble does not preserve sign of while nan construction` - // if (isnan(dval)) { - // self->value.longdouble_value = copysignl(NAN, dval); - // } - // else self->value.longdouble_value = (long double)dval; } } From adf0bc47f63e41563e3c67bda58fb73d36e2f5c1 Mon Sep 17 00:00:00 2001 From: SwayamInSync Date: Sat, 27 Dec 2025 18:14:58 +0000 Subject: [PATCH 37/41] improve signbit assert --- quaddtype/numpy_quaddtype/src/ops.hpp | 25 +++++++++++-------------- quaddtype/tests/test_quaddtype.py | 13 +++++-------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/ops.hpp b/quaddtype/numpy_quaddtype/src/ops.hpp index 55fc17fe..be364b4d 100644 --- a/quaddtype/numpy_quaddtype/src/ops.hpp +++ b/quaddtype/numpy_quaddtype/src/ops.hpp @@ -3,9 +3,6 @@ #include #include "constants.hpp" -// Quad Constants, generated with qutil -#define QUAD_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0) - // Unary Quad Operations typedef Sleef_quad (*unary_op_quad_def)(const Sleef_quad *); // Unary Quad operations with 2 outputs (for modf, frexp) @@ -104,7 +101,7 @@ quad_cbrt(const Sleef_quad *op) // Compute 1/3 as a quad precision constant Sleef_quad three = Sleef_cast_from_int64q1(3); - Sleef_quad one_third = Sleef_divq1_u05(QUAD_ONE, three); + Sleef_quad one_third = Sleef_divq1_u05(QUAD_PRECISION_ONE, three); // Handle negative values: cbrt(-x) = -cbrt(x) if (Sleef_icmpltq1(*op, QUAD_PRECISION_ZERO)) { @@ -126,7 +123,7 @@ quad_square(const Sleef_quad *op) static inline Sleef_quad quad_reciprocal(const Sleef_quad *op) { - return Sleef_divq1_u05(QUAD_ONE, *op); + return Sleef_divq1_u05(QUAD_PRECISION_ONE, *op); } static inline Sleef_quad @@ -492,7 +489,7 @@ quad_signbit(const Sleef_quad *op) { // FIXME @juntyr or @SwayamInSync: replace with binary implementation // once we test big and little endian in CI - Sleef_quad one_signed = Sleef_copysignq1(QUAD_ONE, *op); + Sleef_quad one_signed = Sleef_copysignq1(QUAD_PRECISION_ONE, *op); // signbit(x) = 1 iff copysign(1, x) == -1 return Sleef_icmpltq1(one_signed, QUAD_PRECISION_ZERO); } @@ -600,7 +597,7 @@ quad_floor_divide(const Sleef_quad *a, const Sleef_quad *b) // But NOT when numerator is ±0.0 (then result stays as ±0.0) if (Sleef_icmpeqq1(result, QUAD_PRECISION_ZERO) && quad_signbit(&result) && !Sleef_icmpeqq1(*a, QUAD_PRECISION_ZERO)) { - return Sleef_negq1(QUAD_ONE); // -1.0 + return Sleef_negq1(QUAD_PRECISION_ONE); // -1.0 } return result; @@ -687,7 +684,7 @@ quad_fmod(const Sleef_quad *a, const Sleef_quad *b) if (Sleef_icmpeqq1(result, QUAD_PRECISION_ZERO)) { // Preserve sign of dividend (first argument) - Sleef_quad sign_test = Sleef_copysignq1(QUAD_ONE, *a); + Sleef_quad sign_test = Sleef_copysignq1(QUAD_PRECISION_ONE, *a); if (Sleef_icmpltq1(sign_test, QUAD_PRECISION_ZERO)) { return Sleef_negq1(QUAD_PRECISION_ZERO); // -0.0 } @@ -715,7 +712,7 @@ quad_minimum(const Sleef_quad *in1, const Sleef_quad *in2) } // minimum(-0.0, +0.0) = -0.0 if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { - return Sleef_icmpleq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; + return Sleef_icmpleq1(Sleef_copysignq1(QUAD_PRECISION_ONE, *in1), Sleef_copysignq1(QUAD_PRECISION_ONE, *in2)) ? *in1 : *in2; } return Sleef_fminq1(*in1, *in2); } @@ -728,7 +725,7 @@ quad_maximum(const Sleef_quad *in1, const Sleef_quad *in2) } // maximum(-0.0, +0.0) = +0.0 if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { - return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; + return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_PRECISION_ONE, *in1), Sleef_copysignq1(QUAD_PRECISION_ONE, *in2)) ? *in1 : *in2; } return Sleef_fmaxq1(*in1, *in2); } @@ -741,7 +738,7 @@ quad_fmin(const Sleef_quad *in1, const Sleef_quad *in2) } // fmin(-0.0, +0.0) = -0.0 if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { - return Sleef_icmpleq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; + return Sleef_icmpleq1(Sleef_copysignq1(QUAD_PRECISION_ONE, *in1), Sleef_copysignq1(QUAD_PRECISION_ONE, *in2)) ? *in1 : *in2; } return Sleef_fminq1(*in1, *in2); } @@ -754,7 +751,7 @@ quad_fmax(const Sleef_quad *in1, const Sleef_quad *in2) } // maximum(-0.0, +0.0) = +0.0 if (Sleef_icmpeqq1(*in1, QUAD_PRECISION_ZERO) && Sleef_icmpeqq1(*in2, QUAD_PRECISION_ZERO)) { - return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_ONE, *in1), Sleef_copysignq1(QUAD_ONE, *in2)) ? *in1 : *in2; + return Sleef_icmpgeq1(Sleef_copysignq1(QUAD_PRECISION_ONE, *in1), Sleef_copysignq1(QUAD_PRECISION_ONE, *in2)) ? *in1 : *in2; } return Sleef_fmaxq1(*in1, *in2); } @@ -849,7 +846,7 @@ quad_logaddexp2(const Sleef_quad *x, const Sleef_quad *y) Sleef_quad abs_diff = Sleef_fabsq1(diff); Sleef_quad neg_abs_diff = Sleef_negq1(abs_diff); Sleef_quad exp2_term = Sleef_exp2q1_u10(neg_abs_diff); - Sleef_quad one_plus_exp2 = Sleef_addq1_u05(QUAD_ONE, exp2_term); + Sleef_quad one_plus_exp2 = Sleef_addq1_u05(QUAD_PRECISION_ONE, exp2_term); Sleef_quad log2_term = Sleef_log2q1_u10(one_plus_exp2); Sleef_quad max_val = Sleef_icmpgtq1(*x, *y) ? *x : *y; @@ -872,7 +869,7 @@ quad_heaviside(const Sleef_quad *x1, const Sleef_quad *x2) return *x2; // When x1 == 0, return x2 (even if x2 is NaN) } else { - return QUAD_ONE; + return QUAD_PRECISION_ONE; } } diff --git a/quaddtype/tests/test_quaddtype.py b/quaddtype/tests/test_quaddtype.py index b302405c..578d7f6a 100644 --- a/quaddtype/tests/test_quaddtype.py +++ b/quaddtype/tests/test_quaddtype.py @@ -5459,8 +5459,8 @@ def test_same_value_cast_floats_special_values(self, dtype, val): """Test that special floating-point values roundtrip correctly.""" q = np.array([val], dtype=QuadPrecDType()) result = q.astype(dtype, casting="same_value") - if str(val).startswith("-"): - assert np.signbit(result), f"Sign bit failed for {dtype} with value {val}" + + assert np.signbit(result) == np.signbit(val), f"Sign bit failed for {dtype} with value {val}" if np.isnan(val): assert np.isnan(result), f"NaN failed for {dtype}" else: @@ -5550,8 +5550,7 @@ def test_same_value_cast_strings_enough_width(self, dtype): result = q.astype(dtype, casting="same_value") # Convert back and verify back = result.astype(QuadPrecDType()) - if str(val).startswith("-"): - assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" + assert np.signbit(back[0]) == np.signbit(q[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" if np.isnan(q[0]): assert np.isnan(back[0]), f"NaN roundtrip failed for {dtype}" else: @@ -5566,8 +5565,7 @@ def test_same_value_cast_strings_narrow_width(self, dtype): q = np.array([val], dtype=QuadPrecDType()) result = q.astype(dtype, casting="same_value") back = result.astype(QuadPrecDType()) - if str(val).startswith("-"): - assert np.signbit(back[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" + assert np.signbit(back[0]) == np.signbit(q[0]), f"Sign bit roundtrip failed for {dtype} with value {val}" if np.isnan(q[0]): assert np.isnan(back[0]) else: @@ -5607,8 +5605,7 @@ def test_quad_to_quad_same_value_casting_passing(self, src_backend, dst_backend) result = src.astype(QuadPrecDType(backend=dst_backend), casting="same_value") # Verify value is preserved - if str(val).startswith("-"): - assert np.signbit(result[0]), f"Sign bit failed for {val} in {src_backend} -> {dst_backend}" + assert np.signbit(result[0]) == np.signbit(src[0]), f"Sign bit failed for {val} in {src_backend} -> {dst_backend}" if val in ["nan", "-nan"] : assert np.isnan(result[0]) else: From f6adeda5bd1ed123a4e6046958273e1cf8581b51 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Wed, 14 Jan 2026 09:07:07 +0530 Subject: [PATCH 38/41] dont override error + no same_value for varlen strdtype --- quaddtype/numpy_quaddtype/src/casts.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 034586a6..50da684f 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -124,10 +124,6 @@ quad_to_quad_same_value_check(const quad_value *in_val, QuadBackendType backend_ "QuadPrecision value '%s' cannot be represented exactly in target backend", val_str); } - else { - PyErr_SetString(PyExc_ValueError, - "QuadPrecision value cannot be represented exactly in target backend"); - } return -1; } @@ -465,11 +461,6 @@ quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, n "(string width too narrow or precision loss occurred)", val_str); } - else { - PyErr_SetString(PyExc_ValueError, - "QuadPrecision value cannot be represented exactly in target string dtype " - "(string width too narrow or precision loss occurred)"); - } return -1; } @@ -859,14 +850,6 @@ quad_to_stringdtype_strided_loop(PyArrayMethod_Context *context, char *const dat Py_ssize_t str_size = strnlen(str_buf, QUAD_STR_WIDTH); - // Perform same_value check if requested - if (same_value_casting) { - if (quad_to_string_same_value_check(&in_val, str_buf, str_size, backend) < 0) { - NpyString_release_allocator(allocator); - return -1; - } - } - npy_packed_static_string *out_ps = (npy_packed_static_string *)out_ptr; if (NpyString_pack(allocator, out_ps, str_buf, (size_t)str_size) < 0) { NpyString_release_allocator(allocator); @@ -1422,10 +1405,6 @@ static inline int quad_to_numpy_same_value_check(const quad_value *x, QuadBacken "QuadPrecision value '%s' cannot be represented exactly in the target dtype", val_str); } - else { - PyErr_SetString(PyExc_ValueError, - "QuadPrecision value cannot be represented exactly in the target dtype"); - } return -1; } From c6ec9bca51f0902762115437599411abf076141f Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Wed, 14 Jan 2026 09:55:16 +0530 Subject: [PATCH 39/41] no heap alloc --- quaddtype/numpy_quaddtype/src/casts.cpp | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index 50da684f..c4cffb66 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -414,27 +414,31 @@ static inline int quad_to_string_same_value_check(const quad_value *in_val, const char *str_buf, npy_intp str_len, QuadBackendType backend) { - char *truncated_str = (char *)malloc(str_len + 1); - if (truncated_str == NULL) { - PyErr_NoMemory(); - return -1; + // str_len will never exceed QUAD_STR_WIDTH (50). + char stack_buf[QUAD_STR_WIDTH + 1]; + const char *parse_str; + + if (str_buf[str_len] == '\0') { + // String already properly terminated at str_len, use directly + parse_str = str_buf; + } + else { + // truncated string check + memcpy(stack_buf, str_buf, str_len); + stack_buf[str_len] = '\0'; + parse_str = stack_buf; } - memcpy(truncated_str, str_buf, str_len); - truncated_str[str_len] = '\0'; - // Parse the truncated string back to quad quad_value roundtrip; char *endptr; - int err = NumPyOS_ascii_strtoq(truncated_str, backend, &roundtrip, &endptr); + int err = NumPyOS_ascii_strtoq(parse_str, backend, &roundtrip, &endptr); if (err < 0) { PyErr_Format(PyExc_ValueError, "QuadPrecision value cannot be represented exactly: string '%s' failed to parse back", - truncated_str); - free(truncated_str); + parse_str); return -1; } - free(truncated_str); // Compare original and roundtripped values along with signbit if (backend == BACKEND_SLEEF) { From b22a7b0d0ebcbef8a539eb8747d6d3b36afed4b1 Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Wed, 14 Jan 2026 10:16:50 +0530 Subject: [PATCH 40/41] fixed overriding error cases --- quaddtype/numpy_quaddtype/src/scalar.c | 9 ++------- quaddtype/numpy_quaddtype/src/umath/umath.cpp | 6 +----- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/quaddtype/numpy_quaddtype/src/scalar.c b/quaddtype/numpy_quaddtype/src/scalar.c index f1bcddd4..4d23333e 100644 --- a/quaddtype/numpy_quaddtype/src/scalar.c +++ b/quaddtype/numpy_quaddtype/src/scalar.c @@ -483,14 +483,9 @@ PyObject* quad_to_pylong(Sleef_quad value) PyErr_SetString(PyExc_RuntimeError, "Failed to convert quad to string"); return NULL; } - - PyObject *result = PyLong_FromString(buffer, NULL, 10); - - if (result == NULL) { - PyErr_SetString(PyExc_RuntimeError, "Failed to parse integer string"); - return NULL; - } + // Already raises ValueError and returns NULL on failure + PyObject *result = PyLong_FromString(buffer, NULL, 10); return result; } diff --git a/quaddtype/numpy_quaddtype/src/umath/umath.cpp b/quaddtype/numpy_quaddtype/src/umath/umath.cpp index 68734b70..d9b1fc49 100644 --- a/quaddtype/numpy_quaddtype/src/umath/umath.cpp +++ b/quaddtype/numpy_quaddtype/src/umath/umath.cpp @@ -35,27 +35,22 @@ init_quad_umath(void) } if (init_quad_unary_ops(numpy) < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad unary operations"); goto err; } if (init_quad_unary_props(numpy) < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad unary properties"); goto err; } if (init_quad_binary_ops(numpy) < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad binary operations"); goto err; } if (init_quad_comps(numpy) < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad comparison operations"); goto err; } if (init_matmul_ops(numpy) < 0) { - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad matrix multiplication operations"); goto err; } @@ -63,6 +58,7 @@ init_quad_umath(void) return 0; err: + // Already raises appropriate error Py_DECREF(numpy); return -1; } \ No newline at end of file From a388f3e1f0357064613944cba5988ab5feea0b3b Mon Sep 17 00:00:00 2001 From: swayaminsync Date: Wed, 14 Jan 2026 10:28:03 +0530 Subject: [PATCH 41/41] update comment --- quaddtype/numpy_quaddtype/src/casts.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/quaddtype/numpy_quaddtype/src/casts.cpp b/quaddtype/numpy_quaddtype/src/casts.cpp index c4cffb66..cbc3e02c 100644 --- a/quaddtype/numpy_quaddtype/src/casts.cpp +++ b/quaddtype/numpy_quaddtype/src/casts.cpp @@ -30,7 +30,22 @@ extern "C" { #include "constants.hpp" #define NUM_CASTS 40 // 18 to_casts + 18 from_casts + 1 quad_to_quad + 1 void_to_quad -#define QUAD_STR_WIDTH 50 // 42 is enough for scientific notation float128, just keeping some buffer + +/* +For quad precision scientific notation, we need at most: + +1 character for sign +1 character for leading digit +1 character for decimal point +36 significant digits +1 character for e +1 character for exponent sign +4 characters for exponent (max is 4932) +1 null terminator + +Total: 46 characters, using 50 as a safe buffer +*/ +#define QUAD_STR_WIDTH 50 // forward declarations static inline const char *