Reputation: 108
I want to print floating point numbers which contain variable precision.
I have multiple numbers such as:
0.634564644534135499
0.0005462007746487777
0.028820785252590582
0.0018751147995774936
0.0075146048125540816
0.00046670455
I want to get the same numbers as output using print. I know that the number of decimal places can be fixed using print("{:.19f}".format(0.0005462007746487777))
but I don't want to make the number of decimal places fixed. Since different numbers will have different decimal places
Code
#!/usr/bin/env python3
number_1=0.634564644534135499
number_2=0.0005462007746487777
number_3=0.028820785252590582
number_4=0.0018751147995774936
number_5=0.0075146048125540816
number_6=0.00046670455
print("Number 1: ",number_1)
print("Number 2: ",number_2)
print("Number 3: ",number_3)
print("Number 4: ",number_4)
print("Number 5: ",number_5)
print("Number 6: ",number_6)
Actual Output:
Number 1: 0.6345646445341355
Number 2: 0.0005462007746487777
Number 3: 0.028820785252590582
Number 4: 0.0018751147995774936
Number 5: 0.0075146048125540816
Number 6: 0.00046670455
Required Output:
Number 1: 0.634564644534135499
Number 2: 0.0005462007746487777
Number 3: 0.028820785252590582
Number 4: 0.0018751147995774936
Number 5: 0.0075146048125540816
Number 6: 0.00046670455
What I don't understand is why Number 2(which has higher precision) is being printed correctly but Number 1 losses its precision?
Upvotes: 3
Views: 385
Reputation: 243
So here's a reference image of a number in general (taken off of the web from this link https://www.log2base2.com/number-system/float-to-binary-conversion.html):
Python uses each bit to store the integral part and the fractional part and uses 64 bits to store a floating point value. So if the integral part(absolute value) is high, it then it automatically means that the fractional part will have lesser bits for storage.
In the examples provided by you, since each value has integral part of 0, then the comparison can be done entirely on the fractional part.
So for this case, we will just look at the values that are in the fractional parts.
In the first example (0.634564644534135499) the number of values after the decimal point although are lesser than in the second case (0.0005462007746487777), but the number of bits required are more in the first case than in the second case. So the value stored will be rounded off to ensure that the number of bits are as per the requirement in python.
So to change this, one way that I can think of is to store the values within quotes in the form of a string. It will work, but assuming that you'll be getting the data from elsewhere, not sure if this is very good:
number_1="0.634564644534135499"
# number_3=0.028820785252590582
# number_4=0.0018751147995774936
# number_5=0.0075146048125540816
# number_6=0.00046670455
print("Number 1: ",number_1)
# print("Number 2: ",number_2)
Another way is to use decimal.Decimal('0.634564644534135499'). In case I haven't answered your doubt, as to why does it happen that way let me know please, I'll try and explain better.
Upvotes: 1
Reputation: 95948
Fundamentally what you require is not possible with float
objects, which are basically wrappers around C-doubles, so 64-bit floating point numbers.
There are several things going on here. There is the fundamental problem with mapping a decimal literal input in your source code:
0.634564644534135499
To an actual machine representation, which is not decimal, but binary.
Due to the inherent constraints of the 64-bit floating point format, multiple decimal inputs will be mapped to the same underlying representation:
>>> 0.634564644534135499 == 0.6345646445341355
True
Which is actually neither of those, the exact decimal is actually:
>>> import decimal
>>> decimal.Decimal(0.6345646445341355)
Decimal('0.6345646445341355246227976749651134014129638671875')
>>> decimal.Decimal(0.634564644534135499)
Decimal('0.6345646445341355246227976749651134014129638671875')
What is actually printed when you naively print(some_float)
is going to be a representation which is guaranteed to map back to the same float that produced it if you enter it as a float decimal literal. In fact, since Python 3.1 CPython uses David Gray's algorithm for finding the shortest such representation that preserves the value.
So, as is often the case when encountering troubles due to the inherent limitation of float
, try using decimal.Decimal
. Remember, the input should be a string. The following script:
import decimal
number_1 = decimal.Decimal('0.634564644534135499')
number_2 = decimal.Decimal('0.0005462007746487777')
number_3 = decimal.Decimal('0.028820785252590582')
number_4 = decimal.Decimal('0.0018751147995774936')
number_5 = decimal.Decimal('0.0075146048125540816')
number_6 = decimal.Decimal('0.00046670455')
print("Number 1: ",number_1)
print("Number 2: ",number_2)
print("Number 3: ",number_3)
print("Number 4: ",number_4)
print("Number 5: ",number_5)
print("Number 6: ",number_6)
Prints:
Number 1: 0.634564644534135499
Number 2: 0.0005462007746487777
Number 3: 0.028820785252590582
Number 4: 0.0018751147995774936
Number 5: 0.0075146048125540816
Number 6: 0.00046670455
Upvotes: 3