48

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

4

3 回答 3

92

实际上,事实并非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.1is0.099999999999999811而不是0.0. 这是因为通常的浮点舍入问题。如果您还没有阅读What Every Computer Scientist Should Know About Floating-Point Arithmetic,那么您应该——或者至少是该特定问题的简短维基百科摘要。

还要注意3.5/0.1不是34,它是35。所以,3.5/0.1 * 0.1 + 3.5%0.1is 3.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 为基数有限表示时,你仍然必须处理舍入误差——但舍入误差更符合人类直觉的预期有问题。(还有一些优势Decimal在于float您可以指定显式精度、跟踪有效数字等,并且它实际上在从 2.4 到 3.3 的所有 Python 版本中都是相同的,而有关的详细信息同时float更改了两次。它是只是它并不完美,因为那是不可能的。)但是当您事先知道您的数字都可以精确地以 10 为底表示,并且它们不需要比您配置的精度更多的数字时,它将起作用。

于 2013-02-08T00:44:16.777 回答
12

模数为您rest提供一个除法。3.5除以0.1应该给你35剩下的0。但由于浮点数基于 2 的幂,因此数字并不准确,并且会出现舍入错误。

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

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

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

Python 浮点数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....。在除法的情况下,您甚至会得到最终四舍五入的确切结果。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

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

于 2013-02-08T00:56:39.797 回答
1

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

于 2013-02-08T00:37:38.403 回答