【问题标题】:Python modulo on floatsPython 对浮点数取模
【发布时间】:2013-02-08 00:29:05
【问题描述】:

谁能解释模运算符在 Python 中的工作原理? 我不明白为什么3.5 % 0.1 = 0.1

【问题讨论】:

  • @user202729 确实,接受的答案包含与您建议的答案相关的信息,但同时它具有一些 Python 细节,因为这是一个 Python 问题,并且以模数为中心。

标签: python modulo


【解决方案1】:

实际上,3.5 % 0.1 不是 0.1。您可以很容易地对此进行测试:

>>> print(3.5 % 0.1)
0.1
>>> print(3.5 % 0.1 == 0.1)
False

实际上,在大多数系统上,3.5 % 0.10.099999999999999811。但是,在某些版本的 Python 中,str(0.099999999999999811)0.1

>>> 3.5 % 0.1
0.099999999999999811
>>> repr(3.5 % 0.1)
'0.099999999999999811'
>>> str(3.5 % 0.1)
'0.1'

现在,您可能想知道为什么 3.5 % 0.10.099999999999999811 而不是 0.0。这是因为通常的浮点舍入问题。如果您还没有阅读过What Every Computer Scientist Should Know About Floating-Point Arithmetic,那么您应该阅读——或者至少阅读一下Wikipedia 这个特定问题的简短摘要。

还要注意3.5/0.1 不是34,而是35。所以,3.5/0.1 * 0.1 + 3.5%0.13.5999999999999996,甚至与3.5 都不接近。这对于模数的定义非常重要,但在 Python 和几乎所有其他编程语言中都是错误的。

但是 Python 3 来拯救那里。大多数了解// 的人都知道这是在整数之间进行“整数除法”的方式,但没有意识到这是在任何 类型之间进行模数兼容除法的方式。 3.5//0.134.0,所以 3.5//0.1 * 0.1 + 3.5%0.1 是(至少在一个小的舍入误差范围内)3.5。这已被向后移植到 2.x,因此(取决于您的确切版本和平台)您可以依赖它。如果没有,您可以使用divmod(3.5, 0.1),它返回(在舍入误差内)(34.0, 0.09999999999999981) 一直回到时间的迷雾中。当然,您仍然希望这是 (35.0, 0.0),而不是 (34.0, almost-0.1),但由于舍入错误,您不能拥有它。

如果您正在寻找快速解决方案,请考虑使用 Decimal 类型:

>>> from decimal import Decimal
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
>>> print(Decimal('3.5') % Decimal('0.1'))
0.0
>>> (Decimal(7)/2) % (Decimal(1)/10)
Decimal('0.0')

这不是灵丹妙药——例如,当运算的确切值不能以 10 为基数有限表示时,您仍然需要处理舍入误差——但舍入误差更符合情况人类的直觉预计会有问题。 (Decimalfloat 相比也有优势,因为您可以指定显式精度、跟踪有效数字等,并且它实际上在从 2.4 到 3.3 的所有 Python 版本中都是相同的,而有关 float 的详细信息在同一时间更改了两次。只是它并不完美,因为那是不可能的。)但是当您事先知道您的数字都可以精确地以 10 为基数表示时,它们不需要比精度更多的数字你已经配置好了,它会工作的。

【讨论】:

  • 您错过了一个最重要的事实,即0.1 创建的浮点数略大于0.1。因此35 % 0.100000... = 0.9999999...。你的帖子从来没有提到过,但总是假设0.1 真的是0.9999...。我的回答根本不是这种情况。
  • @bikeshedder:它在哪里假设0.1 真的是0.9999...,甚至0.0999...?如果 的情况,你会在另一个方向出现错误,这不会引起注意。 (如果你不明白为什么,试试看:3.5%0.0999999999 vs.3.5%0.0999999999,然后3.5/0.0999999999 vs.3.5/0.0999999999。第一个显然遵循标准模数定律,减去一点舍入误差;第二个显然打破了整个单位。)
  • 3.5 / 0.1000000... = 34.99999.... 但由于舍入错误,您最终会得到35。所以看起来3.5 / 0.1 给出了35 的确切结果。实际上有两个舍入误差相互抵消。请参阅ideone.com/fTNVho,它很好地展示了这种行为。
  • 不,我没有兴趣继续讨论。其他人都理解我的回答,并认为它回答了这个问题。您添加了恶意的否决票和一堆废话这一事实并不重要。该网站旨在提供有用的答案,而不是最大化代表点或说服巨魔。
  • 我想我现在会坚持使用整数。
【解决方案2】:

Modulo 为您提供一个部门的rest3.5 除以0.1 应该给你350 的其余部分。但由于浮点数基于 2 的幂,因此数字并不精确,并且会出现舍入错误。

如果您需要对十进制数字进行精确除法,请使用十进制模块:

>>> from decimal import Decimal
>>> Decimal('3.5') / Decimal('0.1')
Decimal('35')
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')

当我被抨击我的回答具有误导性时,整个故事就来了:

Python float 0.1 略大于十分之一:

>>> '%.50f' % 0.1
'0.10000000000000000555111512312578270211815834045410'

如果你将浮点数 3.5 除以这个数字,你得到的几乎是 0.1

让我们从数字0.11 开始,继续在两个1 数字之间添加零,以使其更小,同时保持大于0.1

>>> '%.10f' % (3.5 % 0.101)
'0.0660000000'
>>> '%.10f' % (3.5 % 0.1001)
'0.0966000000'
>>> '%.10f' % (3.5 % 0.10001)
'0.0996600000'
>>> '%.10f' % (3.5 % 0.100001)
'0.0999660000'
>>> '%.10f' % (3.5 % 0.1000001)
'0.0999966000'
>>> '%.10f' % (3.5 % 0.10000001)
'0.0999996600'
>>> '%.10f' % (3.5 % 0.100000001)
'0.0999999660'
>>> '%.10f' % (3.5 % 0.1000000001)
'0.0999999966'
>>> '%.10f' % (3.5 % 0.10000000001)
'0.0999999997'
>>> '%.10f' % (3.5 % 0.100000000001)
'0.1000000000'

最后一行给人的印象是我们终于到达了0.1,但更改格式字符串揭示了真正的本质:

>>> '%.20f' % (3.5 % 0.100000000001)
'0.09999999996600009156'

python 的默认浮点格式根本没有显示足够的精度,因此 3.5 % 0.1 = 0.13.5 % 0.1 = 35.0.真的是3.5 % 0.100000... = 0.999999...3.5 / 0.100000... = 34.999999....。在除法的情况下,您甚至会得到 exact 结果,因为 34.9999... 最终会四舍五入到 35.0


有趣的事实:如果您使用一个比0.1 稍小的数字并执行相同的操作,您最终会得到一个比0 稍大的数字:

>>> 1.0 - 0.9
0.09999999999999998
>>> 35.0 % (1.0 - 0.9)
7.771561172376096e-15
>>> '%.20f' % (35.0 % (1.0 - 0.9))
'0.00000000000000777156'

使用 C++,您甚至可以证明 3.5 除以浮点数 0.1 不是 35,而是更小一些。

#include <iostream>
#include <iomanip>

int main(int argc, char *argv[]) {
    // double/float, rounding errors do not cancel out
    std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl;
    // double/double, rounding errors cancel out
    std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl;
    return 0;
}

http://ideone.com/fTNVho

在 Python 中,3.5 / 0.1 为您提供了 35 的确切结果,因为舍入误差相互抵消。真的是3.5 / 0.100000... = 34.9999999...。而34.9999... 最终会很长,以至于你最终得到的正是35。 C++ 程序很好地展示了这一点,因为您可以混合使用 double 和 float 并使用浮点数的精度。

【讨论】:

  • 但是在 Python(和大多数其他语言)中,35.0 除以0.1 得到35,但它得到了0.1 的其余部分,这与模数的定义。
  • 这都是关于四舍五入的。使用浮点数时没有 0.1 这样的东西。十进制数 0.1 由于其性质,根本无法转换为精确的浮点数(请参阅:IEEE floating point)。你最终会得到类似0.0999999999999998110.10000000000000000555... 的东西。如果您想查看号码,请不要使用str(0.1),而是使用"%.100f" % 0.1
  • 我了解舍入(在您发布答案前 15 分钟我已经解释过了)。但这还不是全部。 0.10.0 相差很小(并且35.0 也与34.0 相差很小; abs(0.1-0.0) &lt; eps 对于任何合理的 epsilon 都不会成立。因此,您的回答具有误导性。
  • @abarnert 问题是“为什么 35 % 0.1 = 0.1”,这就是我的答案。我更新了我的答案,以更深入地了解发生了什么以及为什么最终会得到这些奇怪的结果。希望能更好地解释它。
  • 您最新添加的代码,ideone.com/fTNVho具有误导性。它并没有表明“您可以混合使用 double 和 float 并以精度进行游戏”。它所显示的只是(double)0.1f != 0.1(参见ideone.com/lFgkd9)。当你对双精度和浮点数进行算术运算时,C++ 将浮点数加宽为双精度数,然后执行双精度数运算。
【解决方案3】:

这与浮点运算的不精确性有关。 3.5 % 0.1 得到我0.099999999999999811,所以 Python 认为 0.1 最多可以分为 3.5 34 次,剩下 0.099999999999999811。我不确定究竟使用了什么算法来实现这个结果,但这就是要点。

【讨论】:

  • 这几乎是真的,除了它暗示3.5 / 0.134.0,它不是——它是35.0。 (另外,它没有解释为什么他看到的是0.1 而不是0.099999999999999811。)
猜你喜欢
  • 2017-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
  • 2023-04-06
相关资源
最近更新 更多