【问题标题】:Python is producing weird decimals when multiplying [duplicate]Python在乘法时产生奇怪的小数[重复]
【发布时间】:2019-11-30 07:07:05
【问题描述】:

当我执行这 2 行时,我得到 2 个不同的结果。 - 为什么?

item 变量是 numpy.float32 类型

print(item)
print(item * 1)

输出:

0.0006
0.0006000000284984708

我怀疑这与 numpy.float32 类型有关?

如果我尝试将 numpy.float32 转换为 float,我会得到:

item = float(item)
print(item)

输出:

0.0006000000284984708

【问题讨论】:

  • 欢迎来到浮点运算的美妙世界。我认为您没有遇到问题,这就是浮点的工作原理。确实我得到了这些确切的结果
  • 有什么办法可以避免这种情况吗?这给我带来了很多麻烦。
  • 什么样的问题?您可以尝试阅读this answer 来解决似乎类似的问题,也可以尝试this 来规避问题
  • @jottbe 您可以使用np.set_printoptions 调整打印精度
  • 我尝试调整打印选项,但结果相同。

标签: python numpy


【解决方案1】:

除了“数字不匹配”之外,您还没有具体说明问题所在。你如何处理浮点数取决于你的应用程序,但一般来说,你不能依赖精确地比较浮点数。除了一些明显的例外:0 次任何东西都应该是 0,1 次任何东西都应该是 1(还有更多,但让我们停在那里)。那么为什么1*itemitem 不同呢?

>>> item = np.float32(0.0006)
>>> item
0.0006
>>> item*1
0.0006000000284984708

对,这似乎与常识相矛盾。不,这只是错误的方式。做一个实际的比较,世界上一切都还好。

>>> item == item*1
True

数字是一样的。这应该是有道理的——增加浮点的精度不应该改变它的值,乘以1 不应该改变一个数字。

那么,发生了什么事? Numpy 将 np.float32 值转换为 python 浮点数,该浮点数以漂亮的舍入方式打印。但是,item*1np.float64,默认情况下会显示更重要的数字。如果你用相同数量的有效数字打印这两个,你会发现没有真正的区别。

>>> "{:0.015f}".format(item*1)
'0.000600000028498'

>>> "{:0.015f}".format(item)
'0.000600000028498'

就是这样。 python 打印的内容并不意味着完全准确地表示数字。其他答案涉及为什么不能准确表示 0.0006

编辑 舍入不会改变这一点,它只是将item 转换为一个 python 浮点数,它会以舍入方式打印。

>>>  "{:0.015f}".format(round(item, 4))
'0.000600000028498'

【讨论】:

    【解决方案2】:

    不幸的是,您观察到的事情是无法避免的。它与浮点数的内部表示有关。在这种情况下,它甚至与计算问题无关,正如 cmets here 中所建议的那样。

    大多数语言使用的(二进制基数)浮点数表示为(+/- mantisse)*2^exponent。 这里的重要部分是尾数,它不允许精确地表示所有数字。尾数和指数的取值范围取决于您使用的浮点数的位长。指数负责最大和最小可表示数字,而尾数负责可显示数字的精度(松散地说是数字的“粒度”)。

    所以对于你的问题,尾数更重要。如前所述,它就像一个位数组。在一个字节中,一个位的值取决于它的位置 1, 2, 4, ... 在尾数中它是相似的,但是位的值不是 1、2、3、4,而是 1/2、1/4、1/8、...

    因此,如果您想表示 0.75,值 1/2 和 1/4 的位将在您的尾数中设置,并且指数将为 0。简而言之就是这样。 现在,如果您尝试在浮点表示中表示像 0.11 这样的值,您会注意到这是不可能的。不管你用float32还是float64:

    import numpy as np
    item=np.float64('0.11')
    print('{0:2.60f}'.format(item))
    output: 0.110000000000000000555111512312578270211815834045410156250000
    
    item=np.float32('0.11')
    print('{0:2.60f}'.format(item))
    output: 0.109999999403953552246093750000000000000000000000000000000000
    

    顺便说一句。如果要表示值 0.25 (1/4),则不是设置了 1/4 的位,而是设置了 1/2 的位和指数设置为 -1,所以 1/2*2^ (-1) 再次为 0.25。这是在标准化过程中完成的。

    但如果您想提高精度,您可以使用 float64,就像我在示例中所做的那样。会稍微减少这种现象。

    似乎有些系统也支持基于十进制的浮点数。我没有和他们合作过,但他们可能会避免这种问题(不是计算问题,尽管在其他人发布的答案中提到)。

    【讨论】:

    • 请注意,decimal 模块非常适合获取浮点数的十进制扩展,例如print(decimal.Decimal(0.006)),或使用 numpy 数组 arr.astype(np.dtype(Decimal))
    • 感谢您的提示。我从来没有用过这个。但如果你用货币金额计算,它可能是一个不错的选择。所以他也可以使用 tge Decimal 类进行计算,但是他应该使用decimal.Decimal('0.006') 而不是decimal.Decimal(0.006),因为在最后一种情况下,值作为浮点数传递并且损坏已经完成。
    • 除了原生 python 浮点数比十进制模块快几个数量级,当你有几千个值可以使用时,numpy 甚至更快。浮点格式几乎总是正确的使用方式,它们有时会有点令人困惑!
    【解决方案3】:

    您看到两个不同结果的原因是您的变量 item 在 numpy.float32 中,如您所说。 Python内部使用64位浮点数,所以

    print(item)
    

    以 32 位返回(较低精度的)结果,而

    print(item * 1)
    

    首先乘以1,这是一个整数。整数与浮点数相乘是不可能的,因此 Python 将两者都转换为浮点数 - 64 位浮点数,因为您没有指定其他任何内容。结果是一个 64 位浮点数。

    如果您要指定另一种类型的“1”,

    print(item * numpy.float32(1))
    

    返回与print(item) 相同的结果,因为没有类型转换,一切都可以保持在 32 位。

    【讨论】:

      【解决方案4】:

      我似乎无法找到其中的逻辑,但我做了一个解决方法,只需将 numpy.float32 转换为 float 并将数字四舍五入为特定的小数。

      【讨论】:

      • 这是“因为”一个 32 位浮点数不能准确地表示 0.006,最接近的值是您在强制为 64 位浮点数时看到的值(即通过执行 float(item),仅作为 Python本机具有 64 位浮点数)。更多细节在这里:docs.python.org/3/tutorial/floatingpoint.html
      猜你喜欢
      • 2011-11-02
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2015-09-26
      • 1970-01-01
      • 2020-11-18
      • 2023-01-22
      相关资源
      最近更新 更多