将欧元兑换成欧分时有时会漏掉一分钱

Posted

技术标签:

【中文标题】将欧元兑换成欧分时有时会漏掉一分钱【英文标题】:sometimes missing a cent when translating euros to euro cents 【发布时间】:2016-04-20 00:45:16 【问题描述】:

我必须将欧元(在字符串中)转换为欧分(int): 例子:

'12,1' => 1210 '14,51' => 1451

我使用这个python函数:

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

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

更新

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

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

我的问题得到了我先生的解答,他解释了上述结果。

【问题讨论】:

在处理货币时请使用Decimal数据类型! 先乘以100,再取整。 【参考方案1】:

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,当我们减去 33.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 绝对没有任何作用。

【讨论】:

【参考方案2】:

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

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 类,它基本上封装了使用整数表示小数值的所有逻辑。

【讨论】:

我将欧元转换为美分以使用 int 美分进行算术运算。但是“1229,84”出了什么问题。这个数字似乎没有四舍五入的边界问题。我不知道是什么导致了这个数量的问题。 0.84 不能精确地表示为 2 的(负)幂的总和,因此它被四舍五入到下一个最接近的值。 0.84 可以四舍五入到足够接近以至于您不会立即注意到的值,但那是因为您没有“用尽”可用位来存储整数值。尝试查看1229.84 * 100。其中值的整数部分消耗了足够多的位以使错误明显。然后看1229.875 * 100,因为0.875 == 0.5 + 0.25 + 0.125 (1/2 + 1/4 + 1/8)。 “不要对货币使用浮点运算”是有史以来最重要的编程建议。【参考方案3】:

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

Decimal 应该用在任何你做财务计算的地方,或者任何你需要浮点计算的地方,就像人们在学校学习的十进制数学一样(而不是内置 float 类型的二进制浮点行为) .

【讨论】:

以上是关于将欧元兑换成欧分时有时会漏掉一分钱的主要内容,如果未能解决你的问题,请参考以下文章

在java中人民币汇率转换成美元和欧元怎么写打码

如何使用swift制作货币兑换应用程序

BZOJ 1492 [NOI2007]货币兑换Cash 斜率优化DP

if语句C ++期间意外中断

python 汇率 编程

Python就业班——Python基础知识——考试——货币兑换系统(慕K)