diff --git a/src/csrc/dtype.c b/src/csrc/dtype.c index 02debc9..67ba982 100644 --- a/src/csrc/dtype.c +++ b/src/csrc/dtype.c @@ -23,36 +23,6 @@ #include "constants.hpp" #include "utilities.h" -static inline int -quad_load(void *x, char *data_ptr, QuadBackendType backend) -{ - if (data_ptr == NULL || x == NULL) { - return -1; - } - if (backend == BACKEND_SLEEF) { - *(Sleef_quad *)x = *(Sleef_quad *)data_ptr; - } - else { - *(long double *)x = *(long double *)data_ptr; - } - return 0; -} - -static inline int -quad_store(char *data_ptr, void *x, QuadBackendType backend) -{ - if (data_ptr == NULL || x == NULL) { - return -1; - } - if (backend == BACKEND_SLEEF) { - *(Sleef_quad *)data_ptr = *(Sleef_quad *)x; - } - else { - *(long double *)data_ptr = *(long double *)x; - } - return 0; -} - QuadPrecDTypeObject * new_quaddtype_instance(QuadBackendType backend) { @@ -160,13 +130,7 @@ quadprec_setitem(QuadPrecDTypeObject *descr, PyObject *obj, char *dataptr) } } - if (quad_store(dataptr, &value->value, descr->backend) < 0) { - Py_DECREF(value); - char error_msg[100]; - snprintf(error_msg, sizeof(error_msg), "Invalid memory location %p", (void *)dataptr); - PyErr_SetString(PyExc_ValueError, error_msg); - return -1; - } + quad_value_store(dataptr, &value->value, descr->backend); Py_DECREF(value); return 0; @@ -179,13 +143,7 @@ quadprec_getitem(QuadPrecDTypeObject *descr, char *dataptr) if (!new) { return NULL; } - if (quad_load(&new->value, dataptr, descr->backend) < 0) { - Py_DECREF(new); - char error_msg[100]; - snprintf(error_msg, sizeof(error_msg), "Invalid memory location %p", (void *)dataptr); - PyErr_SetString(PyExc_ValueError, error_msg); - return NULL; - } + quad_value_load_canonical(&new->value, dataptr, descr->backend); return (PyObject *)new; } @@ -315,9 +273,10 @@ quadprec_fill(void *buffer, npy_intp length, void *arr_) long double *buf = (long double *)buffer; long double start = buf[0]; long double delta = buf[1] - start; - + char *bytes = (char *)buffer; + for (i = 2; i < length; ++i) { - buf[i] = start + i * delta; + quad_longdouble_store(bytes + i * descr->base.elsize, start + i * delta); } } @@ -362,13 +321,8 @@ quadprec_scanfunc(FILE *fp, void *dptr, char *ignore, PyArray_Descr *descr_gener if (err < 0 || *endptr != '\0') { return 0; /* Return 0 on parse error (no items read) */ } - if (descr->backend == BACKEND_SLEEF) { - *(Sleef_quad *)dptr = val.sleef_value; - } - else { - *(long double *)dptr = val.longdouble_value; - } - + quad_value_store(dptr, &val, descr->backend); + return 1; /* Return 1 on success (1 item read) */ } @@ -381,12 +335,7 @@ quadprec_fromstr(char *s, void *dptr, char **endptr, PyArray_Descr *descr_generi if (err < 0) { return -1; } - if(descr->backend == BACKEND_SLEEF) { - *(Sleef_quad *)dptr = val.sleef_value; - } - else { - *(long double *)dptr = val.longdouble_value; - } + quad_value_store(dptr, &val, descr->backend); return 0; } diff --git a/src/csrc/scalar.c b/src/csrc/scalar.c index 82a3057..86cb49e 100644 --- a/src/csrc/scalar.c +++ b/src/csrc/scalar.c @@ -43,8 +43,7 @@ QuadPrecision_raw_new(QuadBackendType backend) } else { // An 80-bit long double occupies 16 bytes but writes only 10 - memset(&new->value, 0, sizeof(new->value)); - new->value.longdouble_value = 0.0L; + quad_value_set_longdouble(&new->value, 0.0L); } return new; } @@ -760,10 +759,16 @@ QuadPrecision_from_raw_bytes(PyObject *Py_UNUSED(module), PyObject *args) PyBuffer_Release(&view); return NULL; } - unsigned char *dst = (backend == BACKEND_SLEEF) - ? (unsigned char *)&self->value.sleef_value - : (unsigned char *)&self->value.longdouble_value; - quad_copy_canonical(dst, (const unsigned char *)view.buf, expected); + unsigned char raw[sizeof(quad_value)]; + quad_copy_canonical(raw, (const unsigned char *)view.buf, expected); + if (backend == BACKEND_SLEEF) { + memcpy(&self->value.sleef_value, raw, expected); + } + else { + long double value; + memcpy(&value, raw, expected); + quad_value_set_longdouble(&self->value, value); + } PyBuffer_Release(&view); return (PyObject *)self; } diff --git a/src/csrc/umath/binary_ops.cpp b/src/csrc/umath/binary_ops.cpp index b92bbde..9f27ee6 100644 --- a/src/csrc/umath/binary_ops.cpp +++ b/src/csrc/umath/binary_ops.cpp @@ -92,19 +92,17 @@ quad_generic_binop_strided_loop_unaligned(PyArrayMethod_Context *context, char * QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in1, in2, out; while (N--) { - memcpy(&in1, in1_ptr, elem_size); - memcpy(&in2, in2_ptr, elem_size); + quad_value_load(&in1, in1_ptr, backend); + quad_value_load(&in2, in2_ptr, backend); if (backend == BACKEND_SLEEF) { out.sleef_value = sleef_op(&in1.sleef_value, &in2.sleef_value); } else { out.longdouble_value = longdouble_op(&in1.longdouble_value, &in2.longdouble_value); } - memcpy(out_ptr, &out, elem_size); + quad_value_store(out_ptr, &out, backend); in1_ptr += in1_stride; in2_ptr += in2_stride; @@ -134,7 +132,8 @@ quad_generic_binop_strided_loop_aligned(PyArrayMethod_Context *context, char *co *(Sleef_quad *)out_ptr = sleef_op((Sleef_quad *)in1_ptr, (Sleef_quad *)in2_ptr); } else { - *(long double *)out_ptr = longdouble_op((long double *)in1_ptr, (long double *)in2_ptr); + quad_longdouble_store_aligned( + out_ptr, longdouble_op((long double *)in1_ptr, (long double *)in2_ptr)); } in1_ptr += in1_stride; @@ -222,12 +221,10 @@ quad_generic_binop_2out_strided_loop_unaligned(PyArrayMethod_Context *context, c QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in1, in2, out1, out2; while (N--) { - memcpy(&in1, in1_ptr, elem_size); - memcpy(&in2, in2_ptr, elem_size); + quad_value_load(&in1, in1_ptr, backend); + quad_value_load(&in2, in2_ptr, backend); if (backend == BACKEND_SLEEF) { sleef_op(&in1.sleef_value, &in2.sleef_value, &out1.sleef_value, &out2.sleef_value); } @@ -235,8 +232,8 @@ quad_generic_binop_2out_strided_loop_unaligned(PyArrayMethod_Context *context, c longdouble_op(&in1.longdouble_value, &in2.longdouble_value, &out1.longdouble_value, &out2.longdouble_value); } - memcpy(out1_ptr, &out1, elem_size); - memcpy(out2_ptr, &out2, elem_size); + quad_value_store(out1_ptr, &out1, backend); + quad_value_store(out2_ptr, &out2, backend); in1_ptr += in1_stride; in2_ptr += in2_stride; @@ -270,8 +267,10 @@ quad_generic_binop_2out_strided_loop_aligned(PyArrayMethod_Context *context, cha (Sleef_quad *)out1_ptr, (Sleef_quad *)out2_ptr); } else { - longdouble_op((long double *)in1_ptr, (long double *)in2_ptr, - (long double *)out1_ptr, (long double *)out2_ptr); + long double out1, out2; + longdouble_op((long double *)in1_ptr, (long double *)in2_ptr, &out1, &out2); + quad_longdouble_store_aligned(out1_ptr, out1); + quad_longdouble_store_aligned(out2_ptr, out2); } in1_ptr += in1_stride; @@ -338,12 +337,10 @@ quad_ldexp_strided_loop_unaligned(PyArrayMethod_Context *context, char *const da QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in1, out; npy_intp in2_intp; // Platform-native integer (int64 on 64-bit, int32 on 32-bit) while (N--) { - memcpy(&in1, in1_ptr, elem_size); + quad_value_load(&in1, in1_ptr, backend); memcpy(&in2_intp, in2_ptr, sizeof(npy_intp)); int exp_value = (int)in2_intp; @@ -353,7 +350,7 @@ quad_ldexp_strided_loop_unaligned(PyArrayMethod_Context *context, char *const da } else { out.longdouble_value = longdouble_op(&in1.longdouble_value, &exp_value); } - memcpy(out_ptr, &out, elem_size); + quad_value_store(out_ptr, &out, backend); in1_ptr += in1_stride; in2_ptr += in2_stride; @@ -387,7 +384,8 @@ quad_ldexp_strided_loop_aligned(PyArrayMethod_Context *context, char *const data if (backend == BACKEND_SLEEF) { *(Sleef_quad *)out_ptr = sleef_op((Sleef_quad *)in1_ptr, &exp_value); } else { - *(long double *)out_ptr = longdouble_op((long double *)in1_ptr, &exp_value); + quad_longdouble_store_aligned( + out_ptr, longdouble_op((long double *)in1_ptr, &exp_value)); } in1_ptr += in1_stride; diff --git a/src/csrc/umath/matmul.cpp b/src/csrc/umath/matmul.cpp index c04fe06..6d4b5de 100644 --- a/src/csrc/umath/matmul.cpp +++ b/src/csrc/umath/matmul.cpp @@ -470,7 +470,7 @@ naive_matmul_strided_loop(PyArrayMethod_Context *context, char *const data[], sum += a_val * b_val; } - memcpy(C_ij, &sum, sizeof(long double)); + quad_longdouble_store(C_ij, sum); } } } diff --git a/src/csrc/umath/unary_ops.cpp b/src/csrc/umath/unary_ops.cpp index cbfc062..7061294 100644 --- a/src/csrc/umath/unary_ops.cpp +++ b/src/csrc/umath/unary_ops.cpp @@ -61,18 +61,16 @@ quad_generic_unary_op_strided_loop_unaligned(PyArrayMethod_Context *context, cha QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in, out; while (N--) { - memcpy(&in, in_ptr, elem_size); + quad_value_load(&in, in_ptr, backend); if (backend == BACKEND_SLEEF) { out.sleef_value = sleef_op(&in.sleef_value); } else { out.longdouble_value = longdouble_op(&in.longdouble_value); } - memcpy(out_ptr, &out, elem_size); + quad_value_store(out_ptr, &out, backend); in_ptr += in_stride; out_ptr += out_stride; @@ -100,7 +98,7 @@ quad_generic_unary_op_strided_loop_aligned(PyArrayMethod_Context *context, char *(Sleef_quad *)out_ptr = sleef_op((Sleef_quad *)in_ptr); } else { - *(long double *)out_ptr = longdouble_op((long double *)in_ptr); + quad_longdouble_store_aligned(out_ptr, longdouble_op((long double *)in_ptr)); } in_ptr += in_stride; out_ptr += out_stride; @@ -321,19 +319,17 @@ quad_generic_unary_op_2out_strided_loop_unaligned(PyArrayMethod_Context *context QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in, out1, out2; while (N--) { - memcpy(&in, in_ptr, elem_size); + quad_value_load(&in, in_ptr, backend); if (backend == BACKEND_SLEEF) { sleef_op(&in.sleef_value, &out1.sleef_value, &out2.sleef_value); } else { longdouble_op(&in.longdouble_value, &out1.longdouble_value, &out2.longdouble_value); } - memcpy(out1_ptr, &out1, elem_size); - memcpy(out2_ptr, &out2, elem_size); + quad_value_store(out1_ptr, &out1, backend); + quad_value_store(out2_ptr, &out2, backend); in_ptr += in_stride; out1_ptr += out1_stride; @@ -365,7 +361,10 @@ quad_generic_unary_op_2out_strided_loop_aligned(PyArrayMethod_Context *context, sleef_op((Sleef_quad *)in_ptr, (Sleef_quad *)out1_ptr, (Sleef_quad *)out2_ptr); } else { - longdouble_op((long double *)in_ptr, (long double *)out1_ptr, (long double *)out2_ptr); + long double out1, out2; + longdouble_op((long double *)in_ptr, &out1, &out2); + quad_longdouble_store_aligned(out1_ptr, out1); + quad_longdouble_store_aligned(out2_ptr, out2); } in_ptr += in_stride; out1_ptr += out1_stride; @@ -470,20 +469,18 @@ quad_frexp_strided_loop_unaligned(PyArrayMethod_Context *context, char *const da QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; QuadBackendType backend = descr->backend; - size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); - quad_value in, out_mantissa; int out_exp; while (N--) { - memcpy(&in, in_ptr, elem_size); + quad_value_load(&in, in_ptr, backend); if (backend == BACKEND_SLEEF) { out_mantissa.sleef_value = sleef_op(&in.sleef_value, &out_exp); } else { out_mantissa.longdouble_value = longdouble_op(&in.longdouble_value, &out_exp); } - memcpy(out_mantissa_ptr, &out_mantissa, elem_size); + quad_value_store(out_mantissa_ptr, &out_mantissa, backend); memcpy(out_exp_ptr, &out_exp, sizeof(int)); in_ptr += in_stride; @@ -520,7 +517,7 @@ quad_frexp_strided_loop_aligned(PyArrayMethod_Context *context, char *const data } else { long double mantissa = longdouble_op((long double *)in_ptr, &out_exp); - memcpy(out_mantissa_ptr, &mantissa, sizeof(long double)); + quad_longdouble_store_aligned(out_mantissa_ptr, mantissa); } memcpy(out_exp_ptr, &out_exp, sizeof(int)); diff --git a/src/csrc/utilities.c b/src/csrc/utilities.c index dea2e50..9d1a176 100644 --- a/src/csrc/utilities.c +++ b/src/csrc/utilities.c @@ -158,7 +158,7 @@ cstring_to_quad_internal(const char *str, const char *start, QuadBackendType bac } } else { - out_value->longdouble_value = strtold(str, endptr); + quad_value_set_longdouble(out_value, strtold(str, endptr)); } if (endptr && *endptr == str) { @@ -218,7 +218,8 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val out_value->sleef_value = sign > 0 ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; } else { - out_value->longdouble_value = sign > 0 ? strtold("inf", NULL) : strtold("-inf", NULL); + quad_value_set_longdouble(out_value, + sign > 0 ? strtold("inf", NULL) : strtold("-inf", NULL)); } if (endptr) { @@ -252,7 +253,7 @@ NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_val out_value->sleef_value = nan_val; } else { - out_value->longdouble_value = sign < 0 ? -nanl("") : nanl(""); + quad_value_set_longdouble(out_value, sign < 0 ? -nanl("") : nanl("")); } if (endptr) { diff --git a/src/include/quad_common.h b/src/include/quad_common.h index 566269a..c5e5d72 100644 --- a/src/include/quad_common.h +++ b/src/include/quad_common.h @@ -1,12 +1,22 @@ #ifndef _QUADDTYPE_COMMON_H #define _QUADDTYPE_COMMON_H +#include +#include +#include + +#include +#include + +#ifdef _MSC_VER +#include +#endif + #ifdef __cplusplus extern "C" { #endif -#include -#include +#define X87_LONG_DOUBLE_VALUE_BYTES 10 typedef enum { BACKEND_INVALID = -1, @@ -19,6 +29,104 @@ typedef union { long double longdouble_value; } quad_value; +static inline void +quad_value_zero(quad_value *value) +{ + // Make all object-representation bytes observable before assigning a value. +#if defined(__GNUC__) || defined(__clang__) + memset(value, 0, sizeof(*value)); + __asm__ __volatile__("" : : "r"(value) : "memory"); +#elif defined(_MSC_VER) + memset(value, 0, sizeof(*value)); + _ReadWriteBarrier(); +#else + volatile unsigned char *bytes = (volatile unsigned char *)value; + for (size_t i = 0; i < sizeof(*value); i++) { + bytes[i] = 0; + } +#endif +} + +static inline void +quad_longdouble_store(void *dst, long double value) +{ +#if (defined(__i386__) || defined(__x86_64__)) && LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 + // x87 extended precision occupies bytes 0..9; the remaining bytes are padding. + memset(dst, 0, sizeof(value)); + memcpy(dst, &value, X87_LONG_DOUBLE_VALUE_BYTES); +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 + quad_value canonical; + quad_value_zero(&canonical); + canonical.longdouble_value = value; + memcpy(dst, &canonical, sizeof(canonical.longdouble_value)); +#else + memcpy(dst, &value, sizeof(value)); +#endif +} + +static inline void +quad_longdouble_store_aligned(void *dst, long double value) +{ +#if (defined(__i386__) || defined(__x86_64__)) && LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 + *(long double *)dst = value; + memset((unsigned char *)dst + X87_LONG_DOUBLE_VALUE_BYTES, 0, + sizeof(value) - X87_LONG_DOUBLE_VALUE_BYTES); +#else + quad_longdouble_store(dst, value); +#endif +} + +static inline void +quad_value_set_longdouble(quad_value *value, long double input) +{ + quad_longdouble_store_aligned(&value->longdouble_value, input); +} + +static inline void +quad_value_load(quad_value *value, const void *src, QuadBackendType backend) +{ + if (backend == BACKEND_SLEEF) { + memcpy(&value->sleef_value, src, sizeof(value->sleef_value)); + } + else { + memcpy(&value->longdouble_value, src, sizeof(value->longdouble_value)); + } +} + +static inline void +quad_value_load_canonical(quad_value *value, const void *src, QuadBackendType backend) +{ + if (backend == BACKEND_SLEEF) { + memcpy(&value->sleef_value, src, sizeof(value->sleef_value)); + } + else { + long double input; + memcpy(&input, src, sizeof(input)); + quad_value_set_longdouble(value, input); + } +} + +static inline void +quad_value_store(void *dst, const quad_value *value, QuadBackendType backend) +{ + if (backend == BACKEND_SLEEF) { + memcpy(dst, &value->sleef_value, sizeof(value->sleef_value)); + } + else { + quad_longdouble_store(dst, value->longdouble_value); + } +} + +static inline void +quad_value_store_aligned(void *dst, const quad_value *value, QuadBackendType backend) +{ + if (backend == BACKEND_SLEEF) { + memcpy(dst, &value->sleef_value, sizeof(value->sleef_value)); + } + else { + quad_longdouble_store_aligned(dst, value->longdouble_value); + } +} // For IEEE 754 binary128 (quad precision), we need 36 decimal digits // to guarantee round-trip conversion (string -> parse -> equals original value) diff --git a/src/include/utilities.h b/src/include/utilities.h index 6a0384c..bf4b51c 100644 --- a/src/include/utilities.h +++ b/src/include/utilities.h @@ -63,12 +63,7 @@ template static inline void load_quad(const char *ptr, QuadBackendType backend, quad_value *out) { - if (backend == BACKEND_SLEEF) { - out->sleef_value = load(ptr); - } - else { - out->longdouble_value = load(ptr); - } + quad_value_load(out, ptr, backend); } // Store quad_value to memory based on backend and alignment @@ -76,11 +71,11 @@ template static inline void store_quad(char *ptr, const quad_value *val, QuadBackendType backend) { - if (backend == BACKEND_SLEEF) { - store(ptr, val->sleef_value); + if constexpr (Aligned) { + quad_value_store_aligned(ptr, val, backend); } else { - store(ptr, val->longdouble_value); + quad_value_store(ptr, val, backend); } } diff --git a/tests/test_longdouble_padding.py b/tests/test_longdouble_padding.py new file mode 100644 index 0000000..176e707 --- /dev/null +++ b/tests/test_longdouble_padding.py @@ -0,0 +1,188 @@ +import ctypes + +import numpy as np +import pytest + +from numpy_quaddtype import QuadPrecDType, QuadPrecision +from numpy_quaddtype._quaddtype_main import from_raw_bytes + + +X87_LONG_DOUBLE_VALUE_BYTES = 10 +X87_LONG_DOUBLE_STORAGE_BYTES = 16 + + +def x87_longdouble_dtype(): + dtype = QuadPrecDType(backend="longdouble") + if ( + dtype.itemsize != X87_LONG_DOUBLE_STORAGE_BYTES + or np.finfo(np.longdouble).nmant != 63 + ): + pytest.skip("long double is not x87 80-bit stored in 16 bytes") + return dtype + + +def poisoned_array(dtype, aligned): + storage = bytearray(b"\xa5" * (dtype.itemsize + dtype.alignment)) + address = ctypes.addressof(ctypes.c_char.from_buffer(storage)) + offset = (-address) % dtype.alignment + if not aligned: + offset += 1 + array = np.ndarray((1,), dtype=dtype, buffer=storage, offset=offset) + assert array.flags.aligned == aligned + return array, storage, offset + + +def assert_zero_padding(storage, offset=0): + padding = storage[ + offset + X87_LONG_DOUBLE_VALUE_BYTES : offset + X87_LONG_DOUBLE_STORAGE_BYTES + ] + assert bytes(padding) == b"\x00" * ( + X87_LONG_DOUBLE_STORAGE_BYTES - X87_LONG_DOUBLE_VALUE_BYTES + ) + + +def assert_array_padding_zero(array): + raw = array.tobytes() + for start in range(0, len(raw), array.dtype.itemsize): + assert_zero_padding(raw, start) + + +def test_scalar_construction_zeroes_longdouble_padding(): + x87_longdouble_dtype() + + value = QuadPrecision("1.5", backend="longdouble") + + assert_zero_padding(value.__reduce__()[1][0]) + + +@pytest.mark.parametrize("aligned", [True, False]) +def test_setitem_zeroes_longdouble_padding(aligned): + dtype = x87_longdouble_dtype() + array, storage, offset = poisoned_array(dtype, aligned) + + array[0] = QuadPrecision("1.5", backend="longdouble") + + assert array[0] == QuadPrecision("1.5", backend="longdouble") + assert_zero_padding(storage, offset) + + +@pytest.mark.parametrize("aligned", [True, False]) +@pytest.mark.parametrize( + ("operation", "expected"), + [ + (lambda value, out: np.negative(value, out=out), -1.5), + (lambda value, out: np.add(value, value, out=out), 3.0), + (lambda value, out: np.ldexp(value, 2, out=out), 6.0), + ], +) +def test_ufunc_zeroes_longdouble_padding(aligned, operation, expected): + dtype = x87_longdouble_dtype() + value = np.array([1.5], dtype=dtype) + out, storage, offset = poisoned_array(dtype, aligned) + + operation(value, out) + + assert out[0] == QuadPrecision(str(expected), backend="longdouble") + assert_zero_padding(storage, offset) + + +@pytest.mark.parametrize("aligned", [True, False]) +def test_multi_output_ufunc_zeroes_longdouble_padding(aligned): + dtype = x87_longdouble_dtype() + value = np.array([1.5], dtype=dtype) + fractional, fractional_storage, fractional_offset = poisoned_array(dtype, aligned) + integral, integral_storage, integral_offset = poisoned_array(dtype, aligned) + + np.modf(value, out=(fractional, integral)) + + assert fractional[0] == QuadPrecision("0.5", backend="longdouble") + assert integral[0] == QuadPrecision("1.0", backend="longdouble") + assert_zero_padding(fractional_storage, fractional_offset) + assert_zero_padding(integral_storage, integral_offset) + + +@pytest.mark.parametrize("aligned", [True, False]) +def test_binary_multi_output_ufunc_zeroes_longdouble_padding(aligned): + dtype = x87_longdouble_dtype() + dividend = np.array([5.5], dtype=dtype) + divisor = np.array([2.0], dtype=dtype) + quotient, quotient_storage, quotient_offset = poisoned_array(dtype, aligned) + remainder, remainder_storage, remainder_offset = poisoned_array(dtype, aligned) + + np.divmod(dividend, divisor, out=(quotient, remainder)) + + assert quotient[0] == QuadPrecision("2.0", backend="longdouble") + assert remainder[0] == QuadPrecision("1.5", backend="longdouble") + assert_zero_padding(quotient_storage, quotient_offset) + assert_zero_padding(remainder_storage, remainder_offset) + + +@pytest.mark.parametrize("aligned", [True, False]) +def test_frexp_zeroes_longdouble_padding(aligned): + dtype = x87_longdouble_dtype() + value = np.array([6.0], dtype=dtype) + mantissa, storage, offset = poisoned_array(dtype, aligned) + exponent = np.empty(1, dtype=np.int32) + + np.frexp(value, out=(mantissa, exponent)) + + assert mantissa[0] == QuadPrecision("0.75", backend="longdouble") + assert exponent[0] == 3 + assert_zero_padding(storage, offset) + + +@pytest.mark.parametrize("aligned", [True, False]) +@pytest.mark.parametrize("source_backend", [None, "sleef"]) +def test_cast_zeroes_longdouble_padding(aligned, source_backend): + dtype = x87_longdouble_dtype() + if source_backend is None: + source = np.array([1.5], dtype=np.float64) + else: + source = np.array( + [QuadPrecision("1.5", backend=source_backend)], + dtype=QuadPrecDType(backend=source_backend), + ) + out, storage, offset = poisoned_array(dtype, aligned) + + np.copyto(out, source, casting="unsafe") + + assert out[0] == QuadPrecision("1.5", backend="longdouble") + assert_zero_padding(storage, offset) + + +def test_arange_zeroes_longdouble_padding(): + dtype = x87_longdouble_dtype() + + result = np.arange(8, dtype=dtype) + + np.testing.assert_array_equal( + result.astype(np.float64), np.arange(8, dtype=np.float64) + ) + assert_array_padding_zero(result) + + +def test_fromstring_zeroes_longdouble_padding(): + dtype = x87_longdouble_dtype() + + result = np.fromstring("1.5 inf -nan", dtype=dtype, sep=" ") + + assert result[0] == QuadPrecision("1.5", backend="longdouble") + assert np.isposinf(result[1]) + assert np.isnan(result[2]) + assert np.signbit(result[2]) + assert_array_padding_zero(result) + + +def test_from_raw_bytes_zeroes_longdouble_padding(): + x87_longdouble_dtype() + original = QuadPrecision("1.5", backend="longdouble") + raw = bytearray(original.__reduce__()[1][0]) + raw[X87_LONG_DOUBLE_VALUE_BYTES:] = b"\xa5" * ( + X87_LONG_DOUBLE_STORAGE_BYTES - X87_LONG_DOUBLE_VALUE_BYTES + ) + + result = from_raw_bytes(bytes(raw), "longdouble") + result_raw = result.__reduce__()[1][0] + + assert result == original + assert_zero_padding(result_raw)