Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fractions.Fraction() returns different nom., denom. pair when parsing a float or its string representation

I am aware of the nature of floating point math but I still find the following surprising:

from fractions import Fraction

print(Fraction(0.2))       # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2)))  # -> 1/5

print(Fraction(0.2)==Fraction(str(0.2)))  # returns False
print(0.2 == float(str(0.2)))             # but this returns True!

From the documentation I could not find anything that would explain that. It does state:

...In addition, any string that represents a finite value and is accepted by the float constructor is also accepted by the Fraction constructor...

but to me this implies a similar behavior to float() which I just do not see as shown above.

Is there any explanation for this?


It is important to note that the behavior shown above is not specific to the value (0.2) but rather general; everything I tried behaved the same way.


Interestingly enough:

from fractions import Fraction


for x in range(1, 257):
    if Fraction(str(1/x))==Fraction(1/x):
        print(x)

prints only the powers of 2 that are smaller than the selected upper bound:

1
2
4
8
16
32
64
128
256
like image 920
Ma0 Avatar asked Feb 09 '18 13:02

Ma0


People also ask

How to return a fraction in string format?

Given two integers representing the Numerator and Denominator of a fraction, return the fraction in string format. If the fractional part is repeating, enclose the repeating part in parentheses. Attention reader!

What are the parts of a fraction?

Fractions simply tell how many parts of a whole (number or any certain value). In other words, it is also termed as portion or section or division of any quantity. It is denoted by using ‘/’ symbol, such as a/b. For example in 2/4, the number on top is the numerator, while the number below is the denominator.

What is the difference between fraction float and decimal in C++?

class fractions.Fraction (float) : This requires the float instance and a fraction instance with same value is returned. class fractions.Fraction (decimal) : This requires the decimal instance and a fraction instance with same value is returned.

What is fraction in Python?

Source code: Lib/fractions.py The fractions module provides support for rational number arithmetic. A Fraction instance can be constructed from a pair of integers, from another rational number, or from a string.


1 Answers

Have a look at the def __new__(): implementation in fractions.py, if a string is given:

The regex _RATIONAL_FORMAT ( see link if you are interested in the parsing part) puts out numerator as 0 and decimal as 2

Start quote from fractions.py source, with comments by me

elif isinstance(numerator, str):
    # Handle construction from strings.
    m = _RATIONAL_FORMAT.match(numerator)
    if m is None:
        raise ValueError('Invalid literal for Fraction: %r' %
                         numerator)
    numerator = int(m.group('num') or '0')       # 0
    denom = m.group('denom')                     
    if denom:                                    # not true for your case
        denominator = int(denom)
    else:                                        # we are here
        denominator = 1
        decimal = m.group('decimal')             # yep: 2
        if decimal:
            scale = 10**len(decimal)             # thats 10^1
            numerator = numerator * scale + int(decimal)    # thats 0 * 10^1+0 = 10
            denominator *= scale                 # thats 1*2
        exp = m.group('exp')  
        if exp:                                  # false
            exp = int(exp)
            if exp >= 0:
                numerator *= 10**exp
            else:
                denominator *= 10**-exp
    if m.group('sign') == '-':                   # false
        numerator = -numerator

else:
    raise TypeError("argument should be a string "
                    "or a Rational instance")

end quote from source

So '0.2' is parsed to 2 / 10 = 0.2 exactly, not its nearest float approximation wich my calculater puts out at 0,20000000000000001110223024625157

Quintessential: they are not simply using float( yourstring ) but are parsing and calculating the string itself, that is why both differ.

If you use the same constructor and provide a float or decimal the constructor uses the builtin as_integer_ratio() to get numerator and denominator as representation of that number.

The closest the float representation comes to 0.2 is 0,20000000000000001110223024625157 which is exactly what the as_integer_ratio() method returns nominator and denominator for.

As eric-postpischil and mark-dickinson pointed out, this float value is limited by its binary representations to "close to 0.2". When put into str() will be truncated to exact '0.2' - hence the differences between

print(Fraction(0.2))       # -> 3602879701896397/18014398509481984
print(Fraction(str(0.2)))  # -> 1/5
like image 150
Patrick Artner Avatar answered Sep 17 '22 10:09

Patrick Artner