将欧元转换为欧分时有时会漏掉一分钱

sometimes missing a cent when translating euros to euro cents

我必须将欧元(字符串)转换为欧分(整数):
示例:

我使用这个 python 函数:

int(round(float(amount.replace(',', '.')), 2) * 100)

但是这个数量 '1229,84' 结果是:122983

更新

我使用 Wim 的解决方案,因为我在 Python / Jinja 和 javascript 中都使用整数来表示货币 artitmetic。另请参阅 Chepner 的回答。

int(round(100 * float(amout.replace(',', '.')), 2))

我的问题被Mr. Me回答了,他解释了上面的结果。

不要对货币使用浮点运算;无法准确表示的值的舍入误差将导致您看到的损失类型。相反,将字符串表示形式转换为美分的整数,您可以根据需要将其转换为欧元和美分以供显示。

euros, cents = '12,1'.split(',')     # '12,1' -> ('12', '1')
cents = 100*int(euros) + int(cents * 10 if len(cents) == 1 else 1)  # ('12', '1') -> 1210

(请注意,您需要一张支票来处理没有尾随 0 的美分。)

display_str = '%d,%d' % divMod(cents, 100) # 1210 -> (12, 10) -> '12.10'

您还可以使用 decimal 模块中的 Decimal class,它基本上封装了使用整数表示小数值的所有逻辑。

文档怎么说,以及简单的解释

我试了一下,对发生这种情况感到惊讶。所以我转向 documentation,那里有一个小纸条说。

Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.

这是什么意思,大多数小数不能表示为浮点数。好吧,文档后面有一个很棒的 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 问题 提到 SciPy 的 NumPy 浮点数仅此而已而不是内置 float 类型的别名。 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.9 中减去 3 时,我们剩下 -.9。然而,出于某种我不知道的原因,当我们去掉所有整数,只剩下小数部分时,Python 决定向我们展示所有内容——整个尾数。

5 - 这并没有真正影响我们的分析结果,但是当乘以 100 时,隐藏的数字并没有移动 2 位小数,而是改变了一个也很少。

    >>> a = 1229.84
    >>> int(a) - a
    -0.8399999999999181
    >>> a = round(1229.84, 2) * 100
    >>> int(a) - a
    -0.9999999999854481 #I expected -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 绝对没有任何作用。

正如@wim 在评论中提到的,使用来自 stdlib decimal 模块的 Decimal 类型而不是内置的 float 类型。 Decimal 对象不具有浮点数所具有的二进制舍入行为,并且还具有可由用户定义的精度。

Decimal 应该用于进行财务计算的任何地方或任何需要浮点计算的地方,浮点计算的行为类似于人们在学校学习的十进制数学(与内置的二进制浮点行为相反 float类型)。