0

I noticed something strange when calling .as_integer_ratio() on some floats. for example:

(2.2).as_integer_ratio()

This will return a tuple: (2476979795053773, 1125899906842624)

I was wondering if I could get it to return 11/5 directly somehow?

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 2
    Except 11/5 is incorrect. [2.2 can't be represented exactly](https://stackoverflow.com/q/588004/364696) (there is no such thing as 2.2 in IEEE 754 binary floating point). The ratio given is the fully reduced ratio for the representable value closest to 2.2. – ShadowRanger Nov 21 '18 at 00:39

2 Answers2

2

Floating point can't actually represent most values as typed. 2.2 is a convenient shorthand for the closest value to 2.2, but 2.2 doesn't actually exist:

>>> print('{:.16f}'.format(2.2))
2.2000000000000002

If you want decimal accurate representations, you'll need to use the decimal module instead of float:

>>> from decimal import Decimal
>>> Decimal('2.2').as_integer_ratio()  # Constructing from str is *mandatory* for proper precision
(11, 5)
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
0

The answer by @ShadowRanger is correct. The value "2.2" is converted to the closest binary fraction. And .as_integer_ratio() returns that binary fraction. However, there are other nearby rational numbers that result in the same binary representation when converted to a float. It is possible to find a simpler fraction using a Stern-Brocot tree.

gmpy2 has an implementation of the Stern-Brocot algorithm and it is exposed as .as_simple_fraction().

>>> gmpy2.mpfr("2.2").as_integer_ratio()
(mpz(2476979795053773), mpz(1125899906842624))
>>> gmpy2.mpfr("2.2").as_simple_fraction()
mpq(11,5)
casevh
  • 11,093
  • 1
  • 24
  • 35