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.

+3


source to share


4 answers


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

      

+4


source


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)
>>> 

      

+1


source


You can try converting 1.2 to string by dividing it by. and then converting the two strings ("1" and "2") back to the desired format.

Optionally add the second part with "0". will give you a nice format.

0


source


So I just did the following in python terminal and it seemed to work correctly ...

x=1.2
s=str(x).split('.')
i=int(s[0])
d=int(s[1])/10

      

0


source







All Articles