如何可靠地将数字的小数部分和浮点部分分开?

How to reliably separate decimal and floating parts from a number?

这不是this的重复,我会在这里解释。

考虑 x = 1.2。我想把它分成 10.2。我已经尝试了链接问题中概述的所有这些方法:

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

我尝试的所有内容都完全没有 10.2

有什么方法可以可靠地将浮点数转换为其十进制和浮点等价物,而不受浮点表示法限制的阻碍?

我知道这可能是由于数字本身存储方式的限制,所以我愿意接受任何解决这个问题的建议(比如包裹或其他方式)。

编辑:更喜欢不涉及字符串操作的方式,如果可能

解决方案

这看起来像是 hack,但您可以分离字符串形式(实际上是 repr)并将其转换回整数和浮点数:

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

工作原理

以这种方式接近它的原因是 internal algorithm for displaying 1.2 is very sophisticated (a fast variant of David Gay's algorithm)。它努力显示无法准确表示的数字的最短可能表示形式。通过拆分 repr 形式,您可以利用该算法。

在内部,作为 1.2 输入的值存储为二进制分数 5404319552844595 / 4503599627370496,实际上等于 1.1999999999999999555910790149937383830547332763671875。 Gay 算法用于将其显示为字符串 1.2split 然后可靠地提取整数部分。

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)

原理及问题分析

如前所述,您的问题大致可以转化为 "I want to split the integral and fractional parts of the number as it appears visually rather that according to how it is actually stored"。

这样看来,很明显,解决方案涉及解析它的视觉显示方式。虽然它让人感觉像是黑客,但这是利用非常复杂的显示算法并实际匹配您所看到的内容的最直接方式。

除非您手动重现内部显示算法,否则这种方式可能是唯一可靠匹配您所见内容的方式。

备选方案失败

如果您想留在整数领域,可以尝试舍入和减法,但这会给浮点部分带来意想不到的值:

In [9]: round(x)
Out[9]: 1.0

In [10]: x - round(x)
Out[10]: 0.19999999999999996

您可以尝试将 1.2 转换为字符串,在 '.' 上拆分然后将两个字符串(“1”和“2”)转换回您想要的格式。

另外用“0”填充第二部分。会给你一个很好的格式。

所以我只是在 python 终端中执行了以下操作,它似乎工作正常...

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

这是一个没有字符串操作的解决方案(frac_digits 是您可以保证数字的小数部分适合的小数位数):

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