Skip to content

Commit bbdd72c

Browse files
committed
num is losing prec
1 parent 48d2f42 commit bbdd72c

File tree

2 files changed

+459
-1
lines changed

2 files changed

+459
-1
lines changed

quaddtype/numpy_quaddtype/src/scalar.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,227 @@ QuadPrecision_get_imag(QuadPrecisionObject *self, void *closure)
383383
return (PyObject *)QuadPrecision_raw_new(self->backend);
384384
}
385385

386+
// Method implementations for float compatibility
387+
static PyObject *
388+
QuadPrecision_is_integer(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored))
389+
{
390+
Sleef_quad value;
391+
392+
if (self->backend == BACKEND_SLEEF) {
393+
value = self->value.sleef_value;
394+
}
395+
else {
396+
value = Sleef_cast_from_doubleq1((double)self->value.longdouble_value);
397+
}
398+
399+
// Check if value is finite (not inf or nan)
400+
// Using the same approach as quad_isfinite: abs(x) < inf
401+
Sleef_quad abs_value = Sleef_fabsq1(value);
402+
Sleef_quad pos_inf = Sleef_cast_from_doubleq1(INFINITY);
403+
int32_t is_finite = Sleef_icmpltq1(abs_value, pos_inf);
404+
405+
if (!is_finite) {
406+
Py_RETURN_FALSE;
407+
}
408+
409+
// Check if value equals its truncated version
410+
Sleef_quad truncated = Sleef_truncq1(value);
411+
int32_t is_equal = Sleef_icmpeqq1(value, truncated);
412+
413+
if (is_equal) {
414+
Py_RETURN_TRUE;
415+
}
416+
else {
417+
Py_RETURN_FALSE;
418+
}
419+
}
420+
421+
static PyObject *
422+
QuadPrecision_as_integer_ratio(QuadPrecisionObject *self, PyObject *Py_UNUSED(ignored))
423+
{
424+
Sleef_quad value;
425+
426+
if (self->backend == BACKEND_SLEEF) {
427+
value = self->value.sleef_value;
428+
}
429+
else {
430+
value = Sleef_cast_from_doubleq1((double)self->value.longdouble_value);
431+
}
432+
433+
// Check for infinity using: abs(x) == inf
434+
Sleef_quad abs_value = Sleef_fabsq1(value);
435+
Sleef_quad pos_inf = Sleef_cast_from_doubleq1(INFINITY);
436+
int32_t is_inf = Sleef_icmpeqq1(abs_value, pos_inf);
437+
438+
if (is_inf) {
439+
PyErr_SetString(PyExc_OverflowError, "cannot convert Infinity to integer ratio");
440+
return NULL;
441+
}
442+
443+
// Check for NaN using: x != x (NaN property)
444+
int32_t is_nan = !Sleef_icmpeqq1(value, value);
445+
446+
if (is_nan) {
447+
PyErr_SetString(PyExc_ValueError, "cannot convert NaN to integer ratio");
448+
return NULL;
449+
}
450+
451+
// Handle zero
452+
Sleef_quad zero = Sleef_cast_from_int64q1(0);
453+
if (Sleef_icmpeqq1(value, zero)) {
454+
PyObject *numerator = PyLong_FromLong(0);
455+
PyObject *denominator = PyLong_FromLong(1);
456+
if (!numerator || !denominator) {
457+
Py_XDECREF(numerator);
458+
Py_XDECREF(denominator);
459+
return NULL;
460+
}
461+
PyObject *result = PyTuple_Pack(2, numerator, denominator);
462+
Py_DECREF(numerator);
463+
Py_DECREF(denominator);
464+
return result;
465+
}
466+
467+
// Remember the sign and work with absolute value
468+
int is_negative = Sleef_icmpltq1(value, zero);
469+
abs_value = Sleef_fabsq1(value);
470+
471+
// Extract mantissa and exponent using frexp
472+
// frexp returns value = mantissa * 2^exponent, where 0.5 <= |mantissa| < 1
473+
int exponent;
474+
Sleef_quad mantissa = Sleef_frexpq1(abs_value, &exponent);
475+
476+
// For quad precision, we have 113 bits of precision
477+
// Scale mantissa by 2^113 to get all significant bits as an integer
478+
const int QUAD_MANT_DIG = 113;
479+
480+
// We'll build the numerator by converting the mantissa to a hex string
481+
// and parsing it, which preserves all the precision
482+
char hex_buffer[64];
483+
Sleef_snprintf(hex_buffer, sizeof(hex_buffer), "%.28Qa", mantissa);
484+
485+
// Parse the hex representation to get exact mantissa bits
486+
// The format is like "0x1.fffffp+0" or similar
487+
// We need to extract the mantissa and exponent separately
488+
489+
// Instead of using hex parsing (which is complex), let's use a different approach:
490+
// Build the mantissa as an integer by extracting 64-bit chunks
491+
492+
// Multiply mantissa by 2^113 to shift all bits into the integer part
493+
Sleef_quad scaled = Sleef_ldexpq1(mantissa, QUAD_MANT_DIG);
494+
495+
// Now extract the integer value in two 64-bit chunks
496+
// First get the upper 64 bits
497+
Sleef_quad two_64 = Sleef_cast_from_doubleq1(18446744073709551616.0); // 2^64
498+
Sleef_quad upper_part = Sleef_floorq1(Sleef_divq1_u05(scaled, two_64));
499+
uint64_t upper_bits = Sleef_cast_to_uint64q1(upper_part);
500+
501+
// Get the lower 64 bits
502+
Sleef_quad lower_part_quad = Sleef_subq1_u05(scaled, Sleef_mulq1_u05(upper_part, two_64));
503+
uint64_t lower_bits = Sleef_cast_to_uint64q1(lower_part_quad);
504+
505+
// Build Python integer from the two 64-bit parts
506+
PyObject *upper_py = PyLong_FromUnsignedLongLong(upper_bits);
507+
if (!upper_py) {
508+
return NULL;
509+
}
510+
511+
PyObject *shift_64 = PyLong_FromLong(64);
512+
if (!shift_64) {
513+
Py_DECREF(upper_py);
514+
return NULL;
515+
}
516+
517+
PyObject *shifted_upper = PyNumber_Lshift(upper_py, shift_64);
518+
Py_DECREF(upper_py);
519+
Py_DECREF(shift_64);
520+
if (!shifted_upper) {
521+
return NULL;
522+
}
523+
524+
PyObject *lower_py = PyLong_FromUnsignedLongLong(lower_bits);
525+
if (!lower_py) {
526+
Py_DECREF(shifted_upper);
527+
return NULL;
528+
}
529+
530+
PyObject *numerator = PyNumber_Add(shifted_upper, lower_py);
531+
Py_DECREF(shifted_upper);
532+
Py_DECREF(lower_py);
533+
if (!numerator) {
534+
return NULL;
535+
}
536+
537+
// Calculate the final exponent
538+
// value = mantissa * 2^exponent = (numerator / 2^113) * 2^exponent
539+
// value = numerator * 2^(exponent - 113)
540+
int final_exponent = exponent - QUAD_MANT_DIG;
541+
542+
PyObject *denominator;
543+
if (final_exponent >= 0) {
544+
// Shift numerator left
545+
PyObject *shift = PyLong_FromLong(final_exponent);
546+
if (!shift) {
547+
Py_DECREF(numerator);
548+
return NULL;
549+
}
550+
PyObject *new_numerator = PyNumber_Lshift(numerator, shift);
551+
Py_DECREF(shift);
552+
Py_DECREF(numerator);
553+
if (!new_numerator) {
554+
return NULL;
555+
}
556+
numerator = new_numerator;
557+
denominator = PyLong_FromLong(1);
558+
}
559+
else {
560+
// Shift denominator left (denominator = 2^(-final_exponent))
561+
PyObject *shift = PyLong_FromLong(-final_exponent);
562+
if (!shift) {
563+
Py_DECREF(numerator);
564+
return NULL;
565+
}
566+
PyObject *one = PyLong_FromLong(1);
567+
if (!one) {
568+
Py_DECREF(shift);
569+
Py_DECREF(numerator);
570+
return NULL;
571+
}
572+
denominator = PyNumber_Lshift(one, shift);
573+
Py_DECREF(one);
574+
Py_DECREF(shift);
575+
if (!denominator) {
576+
Py_DECREF(numerator);
577+
return NULL;
578+
}
579+
}
580+
581+
// Apply sign
582+
if (is_negative) {
583+
PyObject *new_numerator = PyNumber_Negative(numerator);
584+
Py_DECREF(numerator);
585+
if (!new_numerator) {
586+
Py_DECREF(denominator);
587+
return NULL;
588+
}
589+
numerator = new_numerator;
590+
}
591+
592+
// Create and return the tuple
593+
PyObject *result = PyTuple_Pack(2, numerator, denominator);
594+
Py_DECREF(numerator);
595+
Py_DECREF(denominator);
596+
return result;
597+
}
598+
599+
static PyMethodDef QuadPrecision_methods[] = {
600+
{"is_integer", (PyCFunction)QuadPrecision_is_integer, METH_NOARGS,
601+
"Return True if the value is an integer."},
602+
{"as_integer_ratio", (PyCFunction)QuadPrecision_as_integer_ratio, METH_NOARGS,
603+
"Return a pair of integers whose ratio is exactly equal to the original value."},
604+
{NULL, NULL, 0, NULL} /* Sentinel */
605+
};
606+
386607
static PyGetSetDef QuadPrecision_getset[] = {
387608
{"real", (getter)QuadPrecision_get_real, NULL, "Real part of the scalar", NULL},
388609
{"imag", (getter)QuadPrecision_get_imag, NULL, "Imaginary part of the scalar (always 0 for real types)", NULL},
@@ -400,6 +621,7 @@ PyTypeObject QuadPrecision_Type = {
400621
.tp_as_number = &quad_as_scalar,
401622
.tp_as_buffer = &QuadPrecision_as_buffer,
402623
.tp_richcompare = (richcmpfunc)quad_richcompare,
624+
.tp_methods = QuadPrecision_methods,
403625
.tp_getset = QuadPrecision_getset,
404626
};
405627

0 commit comments

Comments
 (0)