10

I try to represent a floating point number as a ratio of two integers, but for some reason the integers that I get are quite different from what I would expect to see. Can somebody explain this?

>>> value = 3.2
>>> ratios = value.as_integer_ratio()
>>> ratios
(3602879701896397, 1125899906842624)
>>> ratios[0] / ratios[1]
3.2

I would say that (32, 10) or (16, 5) are much better solutions to the problem. What's strange is that if I try to do the same for number like 2.5, the answer is exactly what I would expect

>>> value = 2.5
>>> value.as_integer_ratio()
(5, 2)
Community
  • 1
  • 1
itdxer
  • 1,236
  • 1
  • 12
  • 40

3 Answers3

19

Use the fractions module to simplify fractions:

>>> from fractions import Fraction
>>> Fraction(3.2)
Fraction(3602879701896397, 1125899906842624)
>>> Fraction(3.2).limit_denominator()
Fraction(16, 5)

From the Fraction.limit_denominator() function:

Finds and returns the closest Fraction to self that has denominator at most max_denominator. This method is useful for finding rational approximations to a given floating-point number

Floating point numbers are limited in precision and cannot represent many numbers exactly; what you see is a rounded representation, but the real number is:

>>> format(3.2, '.50f')
'3.20000000000000017763568394002504646778106689453125'

because a floating point number is represented as a sum of binary fractions; 1/5 can only be represented by adding up 1/8 + 1/16 + 1/128 + more binary fractions for increasing exponents of two.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
5

It's not 16/5 because 3.2 isn't 3.2 exactly... it's a floating point rough approximation of it... eg: 3.20000000000000017764

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • But why `2.5` the same as `2.5` and `3.2` not the same? – itdxer Nov 21 '13 at 15:12
  • @itdxer I think it's value is pretty stable for numbers with `.5` as a divisor. They can be represented exactly as you define them. – aIKid Nov 21 '13 at 15:14
  • @itdxer because some numbers can be represented exactly and some can't... See the link that [FallenAngel provided](http://stackoverflow.com/questions/2100490/floating-point-inaccuracy-examples) – Jon Clements Nov 21 '13 at 15:15
  • 1
    Because you can display integers with binary (base 2) but displaying decimal part is hard because you may not exactly write a decimal number in binary (base 2). So programming languages tries to evaluate the value closest to your expected result. – Mp0int Nov 21 '13 at 15:16
3

While using the fractions module, it is better to provide a string instead of a float to avoid floating point representation issues.

For example, if you pass '3.2' instead of 3.2 you get your desired result:

In : fractions.Fraction('3.2')
Out: Fraction(16, 5)

If you already have the value stored in a variable, you can use string formatting as well.

In : value = 3.2

In : fractions.Fraction(f'{value:.2f}')
Out: Fraction(16, 5)
ayhan
  • 70,170
  • 20
  • 182
  • 203