Docs 说了什么,以及一个简单的解释
我试过了,很惊讶会发生这种情况。于是我转向documentation,里面有一个小字条写着。
注意对于浮点数的 round() 行为可能令人惊讶:对于
例如,round(2.675, 2) 给出 2.67 而不是预期的 2.68。这
不是错误:这是因为大多数小数
不能完全表示为浮点数。
现在这是什么意思,大多数小数不能表示为浮点数。好吧,文档后面有一个很棒的 link 解释了这一点,但是由于您可能不是来这里阅读书呆子的技术文档,所以让我总结一下发生了什么。
Python 使用 IEEE-754 浮点标准来表示浮点数。该标准牺牲了速度的准确性。有些数字无法准确表示。例如.1 实际上表示为0.1000000000000000055511151231257827021181583404541015625。有趣的是,二进制中的 .1 实际上是一个无限重复的数字,就像 1/3 是一个无限重复的 .333333。
幕后案例研究
现在谈谈你的具体情况。研究起来很有趣,这就是我发现的。
首先让我们简化您尝试做的事情
>>> amount = '1229,84'
>>> int(round(float(amount.replace(',', '.')), 2) * 100)
>>> 122983
到
>>>int(1229.84 * 100)
>>> 122983
有时 Python1 无法 100% 准确地显示二进制浮点数,原因与我们无法将小数 1/3 显示为小数的原因相同。当这种情况发生时,Python 会隐藏任何多余的数字。 .1 实际上存储为-0.100000000000000092,但如果您在控制台中键入它,Python 会将其显示为 .1。我们可以通过int(1.1) - 1.13 看到这些额外的数字。我们可以将此int(myNum) - myNum 公式应用于大多数浮点数,以查看它们背后的额外隐藏数字。4。在您的情况下,我们将执行以下操作。
>>> int(1229.84) - 1229.84
-0.8399999999999181
1229.84 实际上是1229.8399999999999181。继续。5
>>> 1229.84, 2) * 100
122983.99999999999 #there's all of our hidden digits showing up.
现在进入最后一步。这是我们关心的部分。将其改回整数。
>>> int(122983.99999999999)
122983
它向下取整而不是向上取整,但是,如果我们从未将它乘以 100,那么最后我们仍然会多出 2 个 9,而 Python 会向上取整。
>>> int(122983.9999999999999)
122984
???现在是怎么回事。为什么 Python 向下舍入 122983.99999999999,但向上舍入 122983.9999999999999?好吧,每当 Python 将浮点数转换为整数时,它都会向下舍入。但是,您必须记住,对于 Python 122983.9999999999999,末尾多出两个 99 与 122984.0 是一样的。
>>> 122983.9999999999999
122984.0
>>> a = 122983.9999999999999
>>> int(a) - a
0.0
最后没有两个额外的 99。
>>> 122983.99999999999
122983.99999999999
>>> a=122983.99999999999
>>> int(a) - a
-0.9999999999854481
Python 肯定将122983.9999999999999 视为122984.0 而不是122983.99999999999。现在回到将122983.99999999999 转换为整数。因为我们自己创建了一个小于122984 的小数部分,Python 将其视为与122984 分开的数字,并且因为转换为整数总是导致Python 向下舍入,所以我们得到122983。
哇。经历了很多事情,但我确实学到了很多写出来的东西,我希望你这样做了。所有这一切的解决方案是使用decimal 数字而不是浮点数,这会影响速度以提高准确性。
四舍五入呢?最初的问题也有一些舍入——它没用。见附录6。
解决方案
a) 最简单的解决方案是使用decimal module 而不是浮点数。这是任何财务或会计程序中首选的处理方式。
文档还提到了我总结的以下解决方案。
b) 精确值可以通过myFloat.hex() 和float.fromhex(myHex) 以十六进制形式表示和检索
c) 也可以通过myFloat.as_integer_ratio()获取精确值作为分数
d) 文档简要提到了使用 SciPy 进行浮点运算,但是 SO question 提到 SciPy 的 NumPy 浮点数只不过是内置浮点类型的别名。 decimal module 会是更好的解决方案。
附录
1 - 尽管我经常提到 Python 的行为,但我谈论的是 IEEE-754 浮点标准的一部分,这是主要编程语言用于浮点数的标准.
2 - int(1.1) - 1.1 给了我-0.10000000000000009,但根据documentation .1 真的是0.1000000000000000055511151231257827021181583404541015625
3 - 我们使用int(1.1) - 1.1 而不是int(.1) - .1 因为int(.1) - .1 没有给我们隐藏的数字,但根据文档他们应该仍然存在于.1,因此我比如说int(someNum) -someNum 大部分时间都有效,但并非所有时间都有效。
4 - 当我们使用公式 int(myNum) - myNum 时,发生的事情是将数字转换为整数会将数字向下舍入,因此 int(3.9) 变为 3,当我们减去 3从3.9 我们剩下-.9。然而,出于某种我不知道的原因,当我们去掉所有整数,只剩下小数部分时,Python 决定向我们展示所有内容——整个尾数。
5 - 这并不真正影响我们的分析结果,但是当乘以 100 时,隐藏的数字并没有被移动 2 个小数位,它们也发生了一些变化。
>>> a = 1229.84
>>> int(a) - 一个
-0.8399999999999181
>>> a = 圆形(1229.84, 2) * 100
>>> int(a) - 一个
-0.9999999999854481 #我期望-0.9999999999918100?
6 - 看起来我们可以通过四舍五入到小数点后两位来消除所有这些多余的数字。
>>> round(1229.84, 2) # which is really round(1229.8399999999999181, 2)
1229.84
但是当我们使用int(someNum) - someNum 公式查看隐藏的数字时,它们仍然存在。
>>> a = round(1229.84, 2)
>>> int(a) - a
-0.8399999999999181
这是因为 Python 无法将 1229.84 存储为二进制浮点数。这是不可能的。所以...四舍五入 1229.84 绝对没有任何作用。