如何可靠地从一个数字中分离出小数部分和浮点数部分?

5

这不是此问题的重复,我会在这里解释。

考虑 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

有没有一种可靠的方法将浮点数转换为其十进制和浮点等效值,而不受浮点表示限制的影响?

我理解这可能是由于数字本身存储方式的限制,因此我愿意接受任何建议(例如程序包或其他方式),以克服这一问题。

编辑:如果可能,最好不要涉及字符串操作。


4
但是 1.2 无法被正确地表示。它只是以 1.2 的形式进行格式化。实际上,这个数字是 1.19999... - Willem Van Onsem
1
@WillemVanOnsem 我对任何能让这成为可能的事情都持开放态度...也许一个新的Python包 :) - cs95
当然有一些包可以使用十进制数格式执行计算,或者可以懒惰地工作,因此可以进行任意精度的评估等。但是Python浮点数遵循IEEE-574规范。而0.2在这种格式下无法准确表示。 - Willem Van Onsem
@WillemVanOnsem 是的... 欢迎提供建议! - cs95
1
@cᴏʟᴅsᴘᴇᴇᴅ 那么问题就变得不明确了。您是否仍然希望能够准确地从这个数字1.23456789中分离出小数部分?请注意,您的数字的整数部分越大,其小数部分中存在的不准确性就越多。 - Leon
显示剩余4条评论
4个回答

7

解决方案

看起来可能有些取巧,但您可以将字符串表单(实际上是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

工作原理

采用这种方式的原因是,显示 1.2 的内部算法非常复杂(是 David Gay 算法的快速变体)。它会努力显示不能精确表示的数字的最短可能表示。通过拆分 repr 格式,您可以利用该算法。

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

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)

原理和问题分析

如上所述,您的问题大致翻译为“我想将数字的整数部分和小数部分按照视觉上的显示方式拆分,而不是按照其实际存储方式”。

以这种方式构建问题,很明显解决方案涉及解析它的视觉显示。虽然可能感觉像一个hack,但这是利用非常复杂的显示算法并实际匹配你看到的内容的最直接的方法。

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

替代方案的失败

如果您想保持在整数领域内,您可以尝试四舍五入和减法,但这将给您浮点数部分带来意外的值:

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

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

我知道字符串可以做些事情。但就像你说的那样,这很差劲和不干净。我会看看是否有一种方法不涉及字符串操作,否则我想这可能被视为唯一的方法。 - cs95
你让我难住了。不过,为了给你一些背景,我想扩展我的答案到这个问题。尝试使用浮点数进行复制,并遇到了这个问题。想看看是否有一个简单的Python解决方案,然后再在JS中实现它。 - cs95
@Coldspeed 对于JS,您仍然可以将字符串转换为固定小数位数,并利用其内部舍入。虽然不像Python解决方案那样完美,但总体上应该相当可靠。使用“toFixed(2)”。 - Raymond Hettinger
@Coldspeed 我把早先的留言移动到答案的“理由”部分。您曾要求一种“可靠”的方式。我认为这可能是唯一可靠的方式,可以完全重现您在视觉上看到的内容(除非您痛苦地手动实现Gay算法或等效算法)。如果您同意,请将此答案标记为已接受。 - Raymond Hettinger
@RaymondHettinger 我提供了一种可行的替代方案 - Leon
我很感激你在这个答案中所付出的思考和努力。谢谢你。让我再等一会儿,看看是否还有其他的方法。 - cs95

1
以下是一种无需使用字符串操作的解决方案(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)
>>> 

你确定这对所有情况都适用吗?我怀疑仍然存在分数部分,它们将被舍入为十的整数次幂,但仍会显示奇怪。 - Raymond Hettinger
@RaymondHettinger 对于正确设置的 frac_digits 设置,我的方法与您的方法相同,不同之处在于计算是在不涉及字符串的情况下完成的。您将小数部分作为字符串分离,而我则通过数字方式实现(但需要关于小数部分位数的提示)。 - Leon
在我看来,这种方法的最大缺点通常是要知道(或者可能需要确定)frac_digits的值。在OP的情况下,这很容易,因为明确指定了小数位数,但在现实世界中,这可能不是使用方式。 - martineau

0
你可以尝试将1.2转换为字符串,按照小数点进行分割,然后将两个字符串("1"和"2")转换回你想要的格式。
此外,在第二部分填充'0.'将会给你一个漂亮的格式。

0

所以我刚刚在 Python 终端中执行了以下操作,似乎正常工作...

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

我现在意识到,除以10只有在小数点后面只有一位数字时才有效,因此你可以除以10^(d的长度),或者在字符串前面加一个"0."。然后转换为整数。 - brian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接