@@ -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+
386607static 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