Search code examples
pythonfloating-pointfractions

How to exceed fraction part limit for Float values


I'm currently working on a Python project to convert Hexadecimal fractionals to Binary. So I faced into this problem that you can only store up to the limit of 17 numbers to the fractional part of a Float value. Here is an clear example of what I'm saying

total = 0
for n in range(1, 21):
    total += 10 ** -n
    print(f"{n} - {total}")

Output

 1 - 0.1
 2 - 0.11
 3 - 0.111
 4 - 0.1111
 5 - 0.11111
 6 - 0.111111
 7 - 0.1111111
 8 - 0.11111111
 9 - 0.111111111
10 - 0.11111111109999999
11 - 0.11111111111
12 - 0.111111111111
13 - 0.1111111111111
14 - 0.11111111111111001
15 - 0.11111111111111101
16 - 0.1111111111111111
17 - 0.11111111111111112
18 - 0.11111111111111112
19 - 0.11111111111111112
20 - 0.11111111111111112

As you can see after 17 it doesn't add up. I know this is because of the limit of bytes that uses to store the float data. But I don't know how to solve it. Next thing is the float point errors. As you can see in the 10th 14th and 15th rows the value doesn't show the correct result. For a previous calculation I used the Decimal library to solve this but this time either it doesn't apply or I don't know how to.


Solution

  • Float numbers (i.e. floating point numbers) have limited precision and are usually implemented internally as either a Base 2 mantissa and a power of 2 as the exponent or as a Base 16 mantissa and a power of 16 as the exponent. What can be precisely described in one base with a finite number of "digits", might require an infinite number of digits in another base. For example, 1/3 in Base 10 is .33333333..., i.e. a never-ending sequence of repeating digits while in Base 3 is is just .1.

    Use decimal.Decimal numbers, which can represent any Base 10 number without rounding errors and they can have arbitrary precision.

    from decimal import Decimal
    
    total = Decimal(0)
    addend = Decimal(".1") # Not Decimal(.1)
    for n in range(1, 21):
        total += addend
        print(f"{n} - {total}")
        addend /= 10
    

    Prints:

    1 - 0.1
    2 - 0.11
    3 - 0.111
    4 - 0.1111
    5 - 0.11111
    6 - 0.111111
    7 - 0.1111111
    8 - 0.11111111
    9 - 0.111111111
    10 - 0.1111111111
    11 - 0.11111111111
    12 - 0.111111111111
    13 - 0.1111111111111
    14 - 0.11111111111111
    15 - 0.111111111111111
    16 - 0.1111111111111111
    17 - 0.11111111111111111
    18 - 0.111111111111111111
    19 - 0.1111111111111111111
    20 - 0.11111111111111111111