Skip to content

Commit 6fa5535

Browse files
authored
Merge pull request #195 from SwayamInSync/ldexp
2 parents 77882ca + 3e81b24 commit 6fa5535

File tree

4 files changed

+375
-3
lines changed

4 files changed

+375
-3
lines changed

quaddtype/numpy_quaddtype/src/ops.hpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ quad_positive(const Sleef_quad *op)
2828
static inline Sleef_quad
2929
quad_sign(const Sleef_quad *op)
3030
{
31-
int32_t sign = Sleef_icmpq1(*op, QUAD_ZERO);
31+
int sign = Sleef_icmpq1(*op, QUAD_ZERO);
3232
// sign(x=NaN) = x; otherwise sign(x) in { -1.0; 0.0; +1.0 }
3333
return Sleef_iunordq1(*op, *op) ? *op : Sleef_cast_from_int64q1(sign);
3434
}
@@ -1047,6 +1047,62 @@ quad_spacing(const Sleef_quad *x)
10471047
return result;
10481048
}
10491049

1050+
// Mixed-type operations (quad, int) -> quad
1051+
typedef Sleef_quad (*ldexp_op_quad_def)(const Sleef_quad *, const int *);
1052+
typedef long double (*ldexp_op_longdouble_def)(const long double *, const int *);
1053+
1054+
static inline Sleef_quad
1055+
quad_ldexp(const Sleef_quad *x, const int *exp)
1056+
{
1057+
// ldexp(x, exp) returns x * 2^exp
1058+
// SLEEF expects: Sleef_quad, int
1059+
1060+
// NaN input -> NaN output (with sign preserved)
1061+
if (Sleef_iunordq1(*x, *x)) {
1062+
return *x;
1063+
}
1064+
1065+
// ±0 * 2^exp = ±0 (preserves sign of zero)
1066+
if (Sleef_icmpeqq1(*x, QUAD_ZERO)) {
1067+
return *x;
1068+
}
1069+
1070+
// ±inf * 2^exp = ±inf (preserves sign of infinity)
1071+
if (quad_isinf(x)) {
1072+
return *x;
1073+
}
1074+
1075+
Sleef_quad result = Sleef_ldexpq1(*x, *exp);
1076+
1077+
return result;
1078+
}
1079+
1080+
static inline long double
1081+
ld_ldexp(const long double *x, const int *exp)
1082+
{
1083+
// ldexp(x, exp) returns x * 2^exp
1084+
// stdlib ldexpl expects: long double, int
1085+
1086+
// NaN input -> NaN output
1087+
if (isnan(*x)) {
1088+
return *x;
1089+
}
1090+
1091+
// ±0 * 2^exp = ±0 (preserves sign of zero)
1092+
if (*x == 0.0L) {
1093+
return *x;
1094+
}
1095+
1096+
// ±inf * 2^exp = ±inf (preserves sign of infinity)
1097+
if (isinf(*x)) {
1098+
return *x;
1099+
}
1100+
1101+
long double result = ldexpl(*x, *exp);
1102+
1103+
return result;
1104+
}
1105+
10501106
// Binary long double operations
10511107
typedef long double (*binary_op_longdouble_def)(const long double *, const long double *);
10521108
// Binary long double operations with 2 outputs (for divmod, modf, frexp)

quaddtype/numpy_quaddtype/src/umath/binary_ops.cpp

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.)
286458
template <binary_op_2out_quad_def sleef_op, binary_op_2out_longdouble_def longdouble_op>
287459
int
@@ -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
}

quaddtype/release_tracker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
| nextafter |||
8181
| spacing |||
8282
| modf |||
83-
| ldexp | | |
83+
| ldexp | | |
8484
| frexp | | |
8585
| floor |||
8686
| ceil |||

0 commit comments

Comments
 (0)