float.as_integer_ratio()

The clearest demonstration of what binary floats really hold. The denominator is always a power of two, which is exactly why 0.1 cannot be a tenth.

Float methodPython 2.6+Live demo
Common call
x.as_integer_ratio()
Returns
tuple — exact numerator and denominator, fully reduced
Replaces
Fraction(x), which calls this internally
Watch out
exact does not mean tidy — 0.1 gives a 16-digit pair
float.as_integer_ratio()
→ tuple

Demo

Live evaluation
Try:
Inputs
nfloata number, e.g. 0.5 or 0.1
Output
float(0.5).as_integer_ratio()
(1, 2)

Halves, quarters and whole numbers give the small tidy fractions you would expect, because those values fit binary exactly. Then look at 0.1: the answer is 3602879701896397 over 36028797018963968. That enormous pair is not a bug — it is the closest a double can get to a tenth, and it is what every calculation using 0.1 is really working with. The denominator is always a power of two, which is the whole story of binary floating point.

Common patterns

Convert to an exact Fraction
Fraction uses this internally, so the result carries the float's real value.
from fractions import Fraction
exact = Fraction(*x.as_integer_ratio())
Compare floats without rounding error
Cross-multiplying the ratios is exact where a direct == may not be.
an, ad = a.as_integer_ratio()
bn, bd = b.as_integer_ratio()
equal = an * bd == bn * ad
Explain a float discrepancy
The fastest way to show why two "equal" numbers are not.
print(value.as_integer_ratio())

Examples

1. A half
(0.5).as_integer_ratio()
Returns
(1, 2)
2. A quarter
(0.25).as_integer_ratio()
Returns
(1, 4)
3. A tenth is not
(0.1).as_integer_ratio()
Returns
(3602879701896397, 36028797018963968)
4. Whole number
(2.0).as_integer_ratio()
Returns
(2, 1)
5. Negative
(-0.75).as_integer_ratio()
Returns
(-3, 4)
6. nan raises
float('nan').as_integer_ratio()
Returns
ValueError: cannot convert NaN to integer ratio

Pitfalls

1. Exact is not the same as expected
People reach for this hoping to recover the decimal they typed and get a 16-digit fraction instead. The method is right — the float never held a tenth in the first place.
Expected (1, 10)
(0.1).as_integer_ratio()
(3602879701896397, 36028797018963968)
Start from text
from fractions import Fraction
Fraction('0.1')
Fraction(1, 10)
2. inf and nan raise
Neither has a finite fraction, so both are errors — and they raise DIFFERENT exception types, which matters if you are catching one specifically.
Two exception types
float('inf').as_integer_ratio()
OverflowError: cannot convert Infinity to integer ratio
Guard first
import math
if math.isfinite(x):
    n, d = x.as_integer_ratio()
safe
3. The denominator is always a power of two
It can never be 10, 3 or 7. Any decimal that is not a sum of halves, quarters and eighths gets approximated, which is the root of nearly every float surprise.
No thirds exist
(1 / 3).as_integer_ratio()
(6004799503160661, 18014398509481984)
Use Fraction
from fractions import Fraction
Fraction(1, 3)
Fraction(1, 3)

When to use

Use it
  • Converting a float to an exact Fraction
  • Explaining or debugging floating-point discrepancies
  • Exact comparison or summation without rounding error
  • Teaching what binary floats actually store
Reach for something else
  • You want the decimal you typed → Fraction(str) or decimal.Decimal
  • You know the value is an int → int.as_integer_ratio always gives (n, 1)
  • Values may be inf or nan → check with math.isfinite first

Notes

Complexity
O(1) — reads the mantissa and exponent, then reduces by powers of two
Return
A new tuple in lowest terms; the denominator is always a positive power of two
CPython impl
Objects/floatobject.c :: float_as_integer_ratio
Memory
Allocates a tuple and, for extreme exponents, very large ints
Thread-safe
Yes — floats are immutable

FAQ

Because a tenth cannot be written as a finite sum of powers of two, any more than a third can be written as a finite decimal. The double stores the nearest value it can, and this method reports that value honestly.

(0.1).as_integer_ratio()
# (3602879701896397, 36028797018963968)

History

2.6
float.as_integer_ratio added.
3.8
int gained a matching as_integer_ratio, so both numeric types now answer.