【问题标题】:How to reliably separate decimal and floating parts from a number?如何可靠地将小数和浮点部分与数字分开?
【发布时间】:2017-07-19 15:10:49
【问题描述】:

这不是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

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

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

编辑:希望采用不涉及字符串操作的方式,如果可能

【问题讨论】:

  • 1.2 无法正确表示。它只是像1.2 这样写的格式。事实上号码是1.19999....
  • @WillemVanOnsem 我愿意接受任何让这成为可能的事情......也许是一个新的python包:)
  • 当然有一些包允许使用十进制数格式执行计算,或者可以懒惰地工作,因此可以以任意精度进行评估,等等。但是 python 浮点数根据 IEEE-574 规范工作。而0.2 无法以这种格式精确表示。
  • @WillemVanOnsem 是的...欢迎提出建议!
  • @cᴏʟᴅsᴘᴇᴇᴅ 那么问题就不明确了。您是否仍希望能够将小数部分与此数字完全分开: 1.23456789 ?请注意,数字的整数部分越大,其小数部分的不准确性就越多。

标签: python floating-point ieee-754 python-internals


【解决方案1】:

解决方案

这可能看起来像一个 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

工作原理

以这种方式接近它的原因是用于显示1.2internal algorithm 非常复杂(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)

原理和问题分析

如上所述,您的问题大致转化为“我想拆分数字的整数部分和小数部分,因为它在视觉上显示,而不是根据它的实际存储方式”。

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

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

替代方案失败

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

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

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

【讨论】:

  • 我知道可以用字符串做一些事情。但就像你说的那样,它很老套,很脏。我会看看是否有不涉及字符串操作的方法,否则我想这将被认为是唯一的方法。
  • 你有我。虽然,为了给您一些背景信息,我想将我的答案扩展到this 问题。试图用花车复制并遇到这个问题。在用JS实现之前想看看有没有简单的python解决方案。
  • @Coldspeed 对于 JS,您仍然可以将字符串转换为固定的小数位数并利用其内部舍入。它不会像 Python 解决方案那样完美,但总体上应该相当可靠。使用toFixed(2)
  • @Coldspeed 我将之前的评论移到了答案的“基本原理”部分。您曾要求“可靠”的方式。我认为这可能是唯一准确再现您所看到的内容的可靠方式(除非您痛苦地手动实现 Gay 算法或等效算法)。如果您同意,请将此答案标记为已接受。
  • @RaymondHettinger 我提供了一个working alternative
【解决方案2】:

这是一个没有字符串操作的解决方案(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)
>>> 

【讨论】:

  • 您确定这适用于所有情况吗?我怀疑仍有小数部分会四舍五入到 10 的整数次方,但仍会奇怪地显示。
  • @RaymondHettinger 对于正确设置的frac_digits 设置,我的方法与您的方法相同,不同之处在于计算是在不涉及字符串的情况下完成的。您将小数部分分隔为字符串,我以数字方式进行(但需要提示小数部分中的位数)
  • 在我看来,一般来说,这种方法的最大缺点是知道(或者可能必须确定)frac_digits 的值。这在 OP 的情况下很容易,其中分配了一个显式值小数,但在现实世界中,这可能不会是它的使用方式。
【解决方案3】:

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

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 我现在意识到除以 10 仅在小数点后有一位数字时才有效,因此您可以除以 10^(d 的长度)或用“0”填充字符串。在前面然后转换为 int
    猜你喜欢
    • 1970-01-01
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    • 1970-01-01
    相关资源
    最近更新 更多