0

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.

  • There is a built-in function to convert hexadecimal float strings to (binary) floats: https://docs.python.org/3/library/stdtypes.html#float.fromhex. Do you want to convert that to a binary string (instead of a decimal string)? – chtz Oct 20 '22 at 21:36
  • No, I wanted to build one of my own. Thanks anyway – Sachith Jayalath Oct 21 '22 at 13:17

1 Answers1

1

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
Booboo
  • 38,656
  • 3
  • 37
  • 60
  • "Float numbers [...] are usually implemented internally as a Base 16 mantissa and a power of 16 as the exponent": No, usually floating point numbers are stored with one sign bit, a (biased) power of 2 and a base 2 significant ("mantissa"). You can check `sys.float_info.radix` to see what basis is used and `sys.float_info.mant_dig` how many digits are stored in the significant. – chtz Oct 20 '22 at 21:46
  • @chtz Perhaps if you did a survey of all computers what you say may be so. For what it's worth, however, on an IBM 370 mainframe computer, as one example, it is Base 16. A normalized floating point value that is not 0 will have the first 4 bits of the mantissa with any value between 1 and 15 (Base 10), i.e. .0001 and .1111 in binary. To normalize a floating point value, bits are shifted left 4 bits at a time (not 1 bit at a time) until the most significant 4 bits are non-zero. It's a big and varied world out there. But does it *really* matter for the purposes of the OP's question? – Booboo Oct 20 '22 at 22:02
  • @chtz I have updated the answer per your comment. – Booboo Oct 20 '22 at 22:58
  • Thank you so much. But still, I'm confused about the way you need to apply the `Decimal()` function. Why did you use **.1** in double quotes and not in 0 . Is there any article or something that I can learn how to use this properly? – Sachith Jayalath Oct 26 '22 at 10:43
  • The quantity 1/10, which only takes one digit to precisely represent it as a Base 10 float (i.e. .1), cannot be precisely represented when the underlying representation is a Base 2 or Base 16 number. So if you said `Decimal(.1)` you would be passing a value to the initializer that is not exactly 1/10 ( `print(Decimal(.1))` outputs: 0.1000000000000000055511151231257827021181583404541015625). Integer values pose no such problem – Booboo Oct 26 '22 at 10:56