How to reliably separate decimal and floating parts from a number in Python?
This is not a duplicate of this , I will explain here.
Let's consider x = 1.2
. I would like to split it into 1
and 0.2
. I've tried all of these methods as stated in the linked question:
In [370]: x = 1.2
In [371]: divmod(x, 1)
Out[371]: (1.0, 0.19999999999999996)
In [372]: math.modf(x)
Out[372]: (0.19999999999999996, 1.0)
In [373]: x - int(x)
Out[373]: 0.19999999999999996
In [374]: x - int(str(x).split('.')[0])
Out[374]: 0.19999999999999996
Nothing I try gives me exactly 1
and 0.2
.
Is there a way to reliably convert a floating number to its decimal and floating point equivalents without getting in the way of limiting the floating point representation?
I understand that this could be due to a limitation on how it is stored itself, so I am open to any suggestion (like a package or otherwise) that overcomes this.
Edit: Would prefer a way that doesn't involve string manipulation if possible.
source to share
Decision
This might seem like a hack, but you can strip off the lowercase form (actually undo) and convert it back to int and floats:
In [1]: x = 1.2
In [2]: s = repr(x)
In [3]: p, q = s.split('.')
In [4]: int(p)
Out[4]: 1
In [5]: float('.' + q)
Out[5]: 0.2
How it works
The reason for this approach is that the internal algorithm for mapping is 1.2
very complex (fast version of David Gay's algorithm) . He works hard to show the shortest possible representation of numbers that cannot be accurately represented. When analyzing the form of representation, you use this algorithm.
Internally, the value entered as 1.2
is stored as a binary fraction 5404319552844595 / 4503599627370496
, which is actually equal to 1.1999999999999999555910790149937383830547332763671875
. The Gay algorithm is used to display this as a string 1.2
. The division then reliably extracts the integer part.
In [6]: from decimal import Decimal
In [7]: Decimal(1.2)
Out[7]: Decimal('1.1999999999999999555910790149937383830547332763671875')
In [8]: (1.2).as_integer_ratio()
Out[8]: (5404319552844595, 4503599627370496)
Justification and analysis of problems
As stated, your problem roughly translates to "I want to split the integral and fractional parts of a number as it looks visually and not according to how it is actually stored."
Framed in this way, it is clear that the solution involves an analysis of how it is displayed visually. While this seems like a hack, it is the most direct way to use the most sophisticated display algorithms and actually match what you see.
This way may be the only reliable way to match what you see, unless you manually replicate the internal display algorithms.
Refusal of alternatives
If you want to stay in the realm of integers, you can try rounding and subtracting, but that will give you an unexpected value for the floating point part:
In [9]: round(x)
Out[9]: 1.0
In [10]: x - round(x)
Out[10]: 0.19999999999999996
source to share
Here is a solution without string manipulation ( frac_digits
is the number of decimal digits you can guarantee that the fractional part of your numbers will fit in):
>>> def integer_and_fraction(x, frac_digits=3):
... i = int(x)
... c = 10**frac_digits
... f = round(x*c-i*c)/c
... return (i, f)
...
>>> integer_and_fraction(1.2)
(1, 0.2)
>>> integer_and_fraction(1.2, 1)
(1, 0.2)
>>> integer_and_fraction(1.2, 2)
(1, 0.2)
>>> integer_and_fraction(1.2, 5)
(1, 0.2)
>>>
source to share