【问题标题】:Decimals to 2 places for money in Python 3Python 3 中的小数点到 2 位的钱
【发布时间】:2016-10-01 00:51:23
【问题描述】:

如何使用decimal 模块让我的小数保留在 2 位以表示货币?

我已经设置了精度,并且该死的几乎所有其他东西,但都失败了。

【问题讨论】:

    标签: python python-3.x decimal currency


    【解决方案1】:

    使用金钱时,您通常希望尽可能晚地限制精度,这样乘法等运算就不会聚合舍入误差。在 python 2 和 3 中,您可以 .quantize()Decimal 达到您想要的任何精度:

    unit_price = decimal.Decimal('8.0107')
    quantity = decimal.Decimal('0.056')
    price = unit_price * quantity
    cents = decimal.Decimal('.01')
    money = price.quantize(cents, decimal.ROUND_HALF_UP)
    

    【讨论】:

    • 另外我应该补充一点,当地法律通常会为您提供明确的量化点。
    • +1 这是最好的建议。将精度全局设置为小数点后 2 位是无稽之谈。下一步:“给定单价(4 位小数)和金额(2 dp),确定将购买多少单位(3 dp)”。还要考虑到您可能正在开发一个软件包,您希望该软件包将被多个司法管辖区的多个客户使用——单价中的 dp 数量和单位数量最好是可配置的。
    【解决方案2】:

    除了用于舍入运算的常数外,公认的答案大部分是正确的。您应该使用ROUND_HALF_UP 而不是ROUND_05UP 进行货币操作。根据docs

    十进制。ROUND_HALF_UP

        从零开始舍入到最近。

    十进制。ROUND_05UP

        如果舍入到零后的最后一位数字是 0 或 5,则从零舍入;否则向零舍入。

    如果百分位的数字是 5 或 0,则使用 ROUND_05UP 只会四舍五入(对于正数),这对于货币数学是不正确的。

    这里有一些例子:

    >>> from decimal import Decimal, ROUND_05UP, ROUND_HALF_UP
    >>> cents = Decimal('0.01')
    >>> Decimal('1.995').quantize(cents, ROUND_HALF_UP)
    Decimal('2.00')  # Correct
    >>> Decimal('1.995').quantize(cents, ROUND_05UP)
    Decimal('1.99')  # Incorrect
    >>> Decimal('1.001').quantize(cents, ROUND_HALF_UP)
    Decimal('1.00')  # Correct
    >>> Decimal('1.001').quantize(cents, ROUND_05UP)
    Decimal('1.01')  # Incorrect
    

    【讨论】:

    • 我正在编写一个会计脚本。我没有在所有正确的地方使用 Decimal,但我发现在单个事务中测试多行借方和贷方的总和(必须始终等于零)舍入可笑的小数字(1.8474111129762605e-13),总是 ROUND_05UP给你一分钱。
    【解决方案3】:

    程序员相信金钱的谎言:

    • 货币值可以存储或表示为浮点数。
    • 所有货币的小数精度为 2。
    • 所有 ISO 4217 定义的货币都有小数精度。
    • 所有货币均在 ISO 4217 中定义。
    • 黄金不是货币。
    • 我的系统永远不必处理小数点后两位以上的晦涩货币。
    • 如果交易的货币价值“小”,则浮点值是可以的。
    • 系统将始终处理相同的货币(因此我们不保留货币,仅保留货币价值)。
    • 将货币值存储为有符号长整数将使它们更易于使用,只需在完成所有算术运算后将它们乘以 100。
    • 客户永远不会抱怨我的舍入方法。
    • 当我将应用程序从语言 X 转换为语言 Y 时,我不必验证舍入行为是否相同。
    • 在将货币 A 兑换成货币 B 时,汇率在交易后变得无关紧要。

    【讨论】:

    • "所有货币的小数精度为 2"
    • 虽然这无疑是准确的,但它并不能完全回答问题。
    • 即使它不是我非常喜欢的问题的答案!
    • 投了赞成票,因为这是一件好事。我确信 SO 不像 wiki,但它肯定会在问题的主题上下文中集中很多信息。女巫太有用了;)
    【解决方案4】:

    解决此问题的一种方法是将 cents 中的货币值存储为整数,并且仅在打印值时转换为十进制表示。这称为fixed point arithmetic

    【讨论】:

    • 理解 Decimal 模块背后的概念很有价值。
    • 我不会投反对票,而是会提到这会导致各种其他问题。例如,迟早您将不得不将您的价格乘以数量或汇率等其他分数。这将导致必须截断多余的尾随数字以将小数点移回其正确位置。
    • @patrys:这是一个很好的观点。当然,如果将精度设置为恰好两个位置,那么使用 Decimal 模块也会遇到同样的问题。
    【解决方案5】:
    >>> decimal.getcontext().prec = 2
    >>> d = decimal.Decimal('2.40')
    >>> d/17
    Decimal('0.14')
    

    您只需将精度设置为 2(第一行),所有内容都将使用不超过 2 个小数位

    只是为了比较:

    >>> 2.4 / 17
    0.1411764705882353
    

    【讨论】:

    • 如果原始数字只有 2 并且我想显示为 2.00 会发生什么?
    • @Musaab 对于打印,您应该使用string formatting
    • 你不应该全局改变Decimal的行为,除非你理解可能的结果并且有工具来衡量它(写很多单元测试,单元测试总是好的!)。在应用程序的一端摆弄库的全局设置很容易破坏项目另一部分中看似无关的东西。如果您足够幸运,您还可以更改您使用的第三方模块的行为,而这些模块恰好在内部使用Decimal
    • 如果将精度更改为 2,则会出现不太“类似货币”的意外行为。精度并不意味着“小数位”。尝试 decimal.Decimal('9.99') + decimal.Decimal(0) == 10 并将上下文 prec 设置为 2。
    猜你喜欢
    • 1970-01-01
    • 2014-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多