@@ -282,6 +282,178 @@ quad_generic_binop_2out_strided_loop_aligned(PyArrayMethod_Context *context, cha
282282 return 0 ;
283283}
284284
285+ // todo: I'll preferrable get all this code duplication in templates later
286+ // resolve descriptors for ldexp (QuadPrecDType, int) -> QuadPrecDType
287+ static NPY_CASTING
288+ quad_ldexp_resolve_descriptors (PyObject *self, PyArray_DTypeMeta *const dtypes[],
289+ PyArray_Descr *const given_descrs[],
290+ PyArray_Descr *loop_descrs[], npy_intp *NPY_UNUSED (view_offset))
291+ {
292+ QuadPrecDTypeObject *descr_in1 = (QuadPrecDTypeObject *)given_descrs[0 ];
293+ QuadBackendType target_backend = descr_in1->backend ;
294+
295+ // Input 0: QuadPrecDType
296+ Py_INCREF (given_descrs[0 ]);
297+ loop_descrs[0 ] = given_descrs[0 ];
298+
299+ // Input 1: Use NPY_INTP (int64 on 64-bit, int32 on 32-bit) to match platform integer size
300+ // This ensures we can handle the full range of PyArray_PyLongDType without data loss
301+ loop_descrs[1 ] = PyArray_DescrFromType (NPY_INTP);
302+
303+ // Output: QuadPrecDType with same backend as input
304+ if (given_descrs[2 ] == NULL ) {
305+ loop_descrs[2 ] = (PyArray_Descr *)new_quaddtype_instance (target_backend);
306+ if (!loop_descrs[2 ]) {
307+ return (NPY_CASTING)-1 ;
308+ }
309+ } else {
310+ QuadPrecDTypeObject *descr_out = (QuadPrecDTypeObject *)given_descrs[2 ];
311+ if (descr_out->backend != target_backend) {
312+ loop_descrs[2 ] = (PyArray_Descr *)new_quaddtype_instance (target_backend);
313+ if (!loop_descrs[2 ]) {
314+ return (NPY_CASTING)-1 ;
315+ }
316+ } else {
317+ Py_INCREF (given_descrs[2 ]);
318+ loop_descrs[2 ] = given_descrs[2 ];
319+ }
320+ }
321+ // Return SAFE_CASTING to allow conversion from other integer types to intp
322+ return NPY_SAFE_CASTING;
323+ }
324+
325+ // Strided loop for ldexp (unaligned)
326+ template <ldexp_op_quad_def sleef_op, ldexp_op_longdouble_def longdouble_op>
327+ int
328+ quad_ldexp_strided_loop_unaligned (PyArrayMethod_Context *context, char *const data[],
329+ npy_intp const dimensions[], npy_intp const strides[],
330+ NpyAuxData *auxdata)
331+ {
332+ npy_intp N = dimensions[0 ];
333+ char *in1_ptr = data[0 ];
334+ char *in2_ptr = data[1 ];
335+ char *out_ptr = data[2 ];
336+ npy_intp in1_stride = strides[0 ];
337+ npy_intp in2_stride = strides[1 ];
338+ npy_intp out_stride = strides[2 ];
339+
340+ QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors [0 ];
341+ QuadBackendType backend = descr->backend ;
342+ size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof (Sleef_quad) : sizeof (long double );
343+
344+ quad_value in1, out;
345+ npy_intp in2_intp; // Platform-native integer (int64 on 64-bit, int32 on 32-bit)
346+ while (N--) {
347+ memcpy (&in1, in1_ptr, elem_size);
348+ memcpy (&in2_intp, in2_ptr, sizeof (npy_intp));
349+
350+ int exp_value = (int )in2_intp;
351+
352+ if (backend == BACKEND_SLEEF) {
353+ out.sleef_value = sleef_op (&in1.sleef_value , &exp_value);
354+ } else {
355+ out.longdouble_value = longdouble_op (&in1.longdouble_value , &exp_value);
356+ }
357+ memcpy (out_ptr, &out, elem_size);
358+
359+ in1_ptr += in1_stride;
360+ in2_ptr += in2_stride;
361+ out_ptr += out_stride;
362+ }
363+ return 0 ;
364+ }
365+
366+ // Strided loop for ldexp (aligned)
367+ template <ldexp_op_quad_def sleef_op, ldexp_op_longdouble_def longdouble_op>
368+ int
369+ quad_ldexp_strided_loop_aligned (PyArrayMethod_Context *context, char *const data[],
370+ npy_intp const dimensions[], npy_intp const strides[],
371+ NpyAuxData *auxdata)
372+ {
373+ npy_intp N = dimensions[0 ];
374+ char *in1_ptr = data[0 ];
375+ char *in2_ptr = data[1 ];
376+ char *out_ptr = data[2 ];
377+ npy_intp in1_stride = strides[0 ];
378+ npy_intp in2_stride = strides[1 ];
379+ npy_intp out_stride = strides[2 ];
380+
381+ QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors [0 ];
382+ QuadBackendType backend = descr->backend ;
383+
384+ while (N--) {
385+ npy_intp exp_intp = *(npy_intp *)in2_ptr;
386+ int exp_value = (int )exp_intp;
387+
388+ if (backend == BACKEND_SLEEF) {
389+ *(Sleef_quad *)out_ptr = sleef_op ((Sleef_quad *)in1_ptr, &exp_value);
390+ } else {
391+ *(long double *)out_ptr = longdouble_op ((long double *)in1_ptr, &exp_value);
392+ }
393+
394+ in1_ptr += in1_stride;
395+ in2_ptr += in2_stride;
396+ out_ptr += out_stride;
397+ }
398+ return 0 ;
399+ }
400+
401+ // Create ldexp ufunc
402+ template <ldexp_op_quad_def sleef_op, ldexp_op_longdouble_def longdouble_op>
403+ int
404+ create_quad_ldexp_ufunc (PyObject *numpy, const char *ufunc_name)
405+ {
406+ PyObject *ufunc = PyObject_GetAttrString (numpy, ufunc_name);
407+ if (ufunc == NULL ) {
408+ return -1 ;
409+ }
410+
411+ PyArray_DTypeMeta *dtypes[3 ] = {&QuadPrecDType, &PyArray_PyLongDType, &QuadPrecDType};
412+
413+ PyType_Slot slots[] = {
414+ {NPY_METH_resolve_descriptors, (void *)&quad_ldexp_resolve_descriptors},
415+ {NPY_METH_strided_loop,
416+ (void *)&quad_ldexp_strided_loop_aligned<sleef_op, longdouble_op>},
417+ {NPY_METH_unaligned_strided_loop,
418+ (void *)&quad_ldexp_strided_loop_unaligned<sleef_op, longdouble_op>},
419+ {0 , NULL }};
420+
421+ PyArrayMethod_Spec Spec = {
422+ .name = " quad_ldexp" ,
423+ .nin = 2 ,
424+ .nout = 1 ,
425+ .casting = NPY_NO_CASTING,
426+ .flags = (NPY_ARRAYMETHOD_FLAGS)(NPY_METH_SUPPORTS_UNALIGNED | NPY_METH_IS_REORDERABLE),
427+ .dtypes = dtypes,
428+ .slots = slots,
429+ };
430+
431+ if (PyUFunc_AddLoopFromSpec (ufunc, &Spec) < 0 ) {
432+ return -1 ;
433+ }
434+
435+ PyObject *promoter_capsule =
436+ PyCapsule_New ((void *)&quad_ufunc_promoter, " numpy._ufunc_promoter" , NULL );
437+ if (promoter_capsule == NULL ) {
438+ return -1 ;
439+ }
440+
441+ PyObject *DTypes = PyTuple_Pack (3 , &PyArrayDescr_Type, &PyArray_PyLongDType, &PyArrayDescr_Type);
442+ if (DTypes == 0 ) {
443+ Py_DECREF (promoter_capsule);
444+ return -1 ;
445+ }
446+
447+ if (PyUFunc_AddPromoter (ufunc, DTypes, promoter_capsule) < 0 ) {
448+ Py_DECREF (promoter_capsule);
449+ Py_DECREF (DTypes);
450+ return -1 ;
451+ }
452+ Py_DECREF (promoter_capsule);
453+ Py_DECREF (DTypes);
454+ return 0 ;
455+ }
456+
285457// Create binary ufunc with 2 outputs (generic for divmod, modf, frexp, etc.)
286458template <binary_op_2out_quad_def sleef_op, binary_op_2out_longdouble_def longdouble_op>
287459int
@@ -466,5 +638,8 @@ init_quad_binary_ops(PyObject *numpy)
466638 if (create_quad_binary_2out_ufunc<quad_divmod, ld_divmod>(numpy, " divmod" ) < 0 ) {
467639 return -1 ;
468640 }
641+ if (create_quad_ldexp_ufunc<quad_ldexp, ld_ldexp>(numpy, " ldexp" ) < 0 ) {
642+ return -1 ;
643+ }
469644 return 0 ;
470645}
0 commit comments