@@ -267,6 +267,147 @@ create_quad_logical_not_ufunc(PyObject *numpy, const char *ufunc_name)
267267 return 0 ;
268268}
269269
270+ // Resolver for unary ufuncs with 2 outputs (like modf)
271+ static NPY_CASTING
272+ quad_unary_op_2out_resolve_descriptors (PyObject *self, PyArray_DTypeMeta *const dtypes[],
273+ PyArray_Descr *const given_descrs[], PyArray_Descr *loop_descrs[],
274+ npy_intp *NPY_UNUSED (view_offset))
275+ {
276+ // Input descriptor
277+ Py_INCREF (given_descrs[0 ]);
278+ loop_descrs[0 ] = given_descrs[0 ];
279+
280+ // Output descriptors (2 outputs)
281+ for (int i = 1 ; i < 3 ; i++) {
282+ if (given_descrs[i] == NULL ) {
283+ Py_INCREF (given_descrs[0 ]);
284+ loop_descrs[i] = given_descrs[0 ];
285+ }
286+ else {
287+ Py_INCREF (given_descrs[i]);
288+ loop_descrs[i] = given_descrs[i];
289+ }
290+ }
291+
292+ QuadPrecDTypeObject *descr_in = (QuadPrecDTypeObject *)given_descrs[0 ];
293+ QuadPrecDTypeObject *descr_out1 = (QuadPrecDTypeObject *)loop_descrs[1 ];
294+ QuadPrecDTypeObject *descr_out2 = (QuadPrecDTypeObject *)loop_descrs[2 ];
295+
296+ if (descr_in->backend != descr_out1->backend || descr_in->backend != descr_out2->backend ) {
297+ return NPY_UNSAFE_CASTING;
298+ }
299+
300+ return NPY_NO_CASTING;
301+ }
302+
303+ // Strided loop for unary ops with 2 outputs (unaligned)
304+ template <unary_op_2out_quad_def sleef_op, unary_op_2out_longdouble_def longdouble_op>
305+ int
306+ quad_generic_unary_op_2out_strided_loop_unaligned (PyArrayMethod_Context *context, char *const data[],
307+ npy_intp const dimensions[], npy_intp const strides[],
308+ NpyAuxData *auxdata)
309+ {
310+ npy_intp N = dimensions[0 ];
311+ char *in_ptr = data[0 ];
312+ char *out1_ptr = data[1 ];
313+ char *out2_ptr = data[2 ];
314+ npy_intp in_stride = strides[0 ];
315+ npy_intp out1_stride = strides[1 ];
316+ npy_intp out2_stride = strides[2 ];
317+
318+ QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors [0 ];
319+ QuadBackendType backend = descr->backend ;
320+ size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof (Sleef_quad) : sizeof (long double );
321+
322+ quad_value in, out1, out2;
323+ while (N--) {
324+ memcpy (&in, in_ptr, elem_size);
325+ if (backend == BACKEND_SLEEF) {
326+ sleef_op (&in.sleef_value , &out1.sleef_value , &out2.sleef_value );
327+ }
328+ else {
329+ longdouble_op (&in.longdouble_value , &out1.longdouble_value , &out2.longdouble_value );
330+ }
331+ memcpy (out1_ptr, &out1, elem_size);
332+ memcpy (out2_ptr, &out2, elem_size);
333+
334+ in_ptr += in_stride;
335+ out1_ptr += out1_stride;
336+ out2_ptr += out2_stride;
337+ }
338+ return 0 ;
339+ }
340+
341+ // Strided loop for unary ops with 2 outputs (aligned)
342+ template <unary_op_2out_quad_def sleef_op, unary_op_2out_longdouble_def longdouble_op>
343+ int
344+ quad_generic_unary_op_2out_strided_loop_aligned (PyArrayMethod_Context *context, char *const data[],
345+ npy_intp const dimensions[], npy_intp const strides[],
346+ NpyAuxData *auxdata)
347+ {
348+ npy_intp N = dimensions[0 ];
349+ char *in_ptr = data[0 ];
350+ char *out1_ptr = data[1 ];
351+ char *out2_ptr = data[2 ];
352+ npy_intp in_stride = strides[0 ];
353+ npy_intp out1_stride = strides[1 ];
354+ npy_intp out2_stride = strides[2 ];
355+
356+ QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors [0 ];
357+ QuadBackendType backend = descr->backend ;
358+
359+ while (N--) {
360+ if (backend == BACKEND_SLEEF) {
361+ sleef_op ((Sleef_quad *)in_ptr, (Sleef_quad *)out1_ptr, (Sleef_quad *)out2_ptr);
362+ }
363+ else {
364+ longdouble_op ((long double *)in_ptr, (long double *)out1_ptr, (long double *)out2_ptr);
365+ }
366+ in_ptr += in_stride;
367+ out1_ptr += out1_stride;
368+ out2_ptr += out2_stride;
369+ }
370+ return 0 ;
371+ }
372+
373+ // Create unary ufunc with 2 outputs
374+ template <unary_op_2out_quad_def sleef_op, unary_op_2out_longdouble_def longdouble_op>
375+ int
376+ create_quad_unary_2out_ufunc (PyObject *numpy, const char *ufunc_name)
377+ {
378+ PyObject *ufunc = PyObject_GetAttrString (numpy, ufunc_name);
379+ if (ufunc == NULL ) {
380+ return -1 ;
381+ }
382+
383+ // 1 input, 2 outputs
384+ PyArray_DTypeMeta *dtypes[3 ] = {&QuadPrecDType, &QuadPrecDType, &QuadPrecDType};
385+
386+ PyType_Slot slots[] = {
387+ {NPY_METH_resolve_descriptors, (void *)&quad_unary_op_2out_resolve_descriptors},
388+ {NPY_METH_strided_loop,
389+ (void *)&quad_generic_unary_op_2out_strided_loop_aligned<sleef_op, longdouble_op>},
390+ {NPY_METH_unaligned_strided_loop,
391+ (void *)&quad_generic_unary_op_2out_strided_loop_unaligned<sleef_op, longdouble_op>},
392+ {0 , NULL }};
393+
394+ PyArrayMethod_Spec Spec = {
395+ .name = " quad_unary_2out" ,
396+ .nin = 1 ,
397+ .nout = 2 ,
398+ .casting = NPY_NO_CASTING,
399+ .flags = NPY_METH_SUPPORTS_UNALIGNED,
400+ .dtypes = dtypes,
401+ .slots = slots,
402+ };
403+
404+ if (PyUFunc_AddLoopFromSpec (ufunc, &Spec) < 0 ) {
405+ return -1 ;
406+ }
407+
408+ return 0 ;
409+ }
410+
270411int
271412init_quad_unary_ops (PyObject *numpy)
272413{
@@ -394,5 +535,10 @@ init_quad_unary_ops(PyObject *numpy)
394535 return -1 ;
395536 }
396537
538+ // Unary operations with 2 outputs
539+ if (create_quad_unary_2out_ufunc<quad_modf, ld_modf>(numpy, " modf" ) < 0 ) {
540+ return -1 ;
541+ }
542+
397543 return 0 ;
398544}
0 commit comments