【问题标题】:Best way to store currency values in C++在 C++ 中存储货币值的最佳方法
【发布时间】:2010-09-14 00:34:36
【问题描述】:

我知道浮点数不适合存储货币值,因为舍入错误。有没有一种标准的方式来用 C++ 来表示金钱?

我查看了 boost 库并没有发现任何关于它的信息。在 java 中,似乎 BigInteger 是一种方式,但我在 C++ 中找不到等价物。我可以编写自己的货币类,但如果有测试,我宁愿不这样做。

【问题讨论】:

  • 有关信息,使用二进制表示或十进制表示不会有或多或少的舍入误差(参见 1/3=0.333...)。使用十进制表示只会让您遇到与手动操作相同的舍入错误。 (更容易检查/匹配结果)
  • @Offirmo:是的。但是,如果您进行财务计算,很多错误可能源于必须将十进制货币转换为二进制货币这一事实。

标签: c++ currency


【解决方案1】:

这取决于您对舍入的业务要求。最安全的方法是存储具有所需精度的整数并知道何时/如何应用舍入。

【讨论】:

  • 虽然在转换问题方面会变得昂贵。每次对值执行任何操作时都会进行转换,因为系统中的每个浮点值不太可能都是这种整数。
  • 在我的回答中,值的精度等于最不精确计算的精度。 Integer * Float 将使用浮点精度。对于 C++,整个链应该是长双精度。
  • 您似乎没有意识到,Orion 并非所有值都可以存储在浮点数中。因此,如果您不知道何时何地四舍五入以清除错误,那么奇怪的小数学错误可能会潜入您的计算中。
【解决方案2】:

我建议您为美分而不是美元的数量保留一个变量。那应该消除舍入误差。以标准美元/美分格式显示它应该是一个视图问题。

【讨论】:

  • 这实际上并不能解决问题,因为您通常需要做的不仅仅是添加这些数字,然后您就会遇到问题,因为您将失去精度。 100.25 美元转换为 10025 * 0.0745234 APR 会导致问题。
  • 如果我没记错的话,某处有一个标准说您应该为常见操作保留至少 4 位数字 - 这就是 COM 的“货币”给您 4 位的原因。如果涉及外币,您会可能需要更多。
  • 我已经在对这个问题的回答中解释了基于精度的计算中精度最低的问题。最终,即使您以整数形式存储数字,您也将不得不以其他方式进行计算。不管是什么东西都应该是存储机制。
  • @Joe:4 位小数确实是最小值。我最终使用 6 进行计算,以获得支票操作的便士分辨率。但是,除非您以整数形式进行所有数学运算,否则您将遇到问题,因为如果您(隐式或显式)强制转换,您最终将进入浮点领域。
【解决方案3】:

在实际的金融系统中处理过这个问题后,我可以告诉您,您可能希望使用至少 6 位小数精度的数字(假设为美元)。希望既然你在谈论货币价值,你就不会在这里失控。有向 C++ 添加十进制类型的建议,但我不知道实际存在的任何类型。

此处使用的最佳原生 C++ 类型是 long double。

仅使用 int 的其他方法的问题在于,您必须存储的不仅仅是美分。金融交易通常会乘以非整数值,这会给您带来麻烦,因为 100.25 美元转换为 10025 * 0.000123523(例如 APR)会导致问题。你最终会在浮点土地上结束,转换会花费你很多。

现在问题在大多数简单的情况下都不会发生。我给你举个准确的例子:

假设有几千个货币值,如果您将每个值乘以一个百分比,然后将它们相加,则最终得到的数字与如果您没有保留足够的小数位而将总和乘以该百分比不同。现在这可能在某些情况下有效,但您通常会很快减少几美分。根据我的一般经验,确保您保持最多 6 位小数的精度(确保剩余的精度可用于整数部分)。

还要了解,如果您以不太精确的方式进行数学运算,则存储它的类型并不重要。如果您的数学是在单精度土地上完成的,那么您是否以双精度存储它并不重要。您的精度将是最不精确的计算。


话虽如此,如果你只做简单的加法或减法运算,然后存储数字,那么你会没事的,但一旦出现比这更复杂的东西,你就会有麻烦了.

【讨论】:

  • 您能否将您的反对意见扩大到整数,或提供参考?您提供的示例计算导致使用整数的结果为 0.01 美元或 1。我不清楚为什么这不是正确的答案。
  • 见上面的例子。我可以提供更多,但在这种情况下,通常很简单。我编写了财务预测软件,你无法摆脱整数和四舍五入。您不仅需要存储美分,还需要存储小数美分。最终四舍五入的问题会让你受益。
  • 我已经编写了一些销售点软件,我对这个问题的解决方案(表现为 sum(discounts-per-line-item) != discount-on-order-total)是确保您始终按照您的意思进行计算。问题空间应规定小百分比的总和或总和的百分比。
  • @Jeffrey(和其他人)——除了 Orion 已经说过的,金融系统需要能够处理非常广泛的数字。股票市场上的股票(尤其是外汇汇率)计算为几分之一便士(0.000001 美元),而津巴布韦元等其他货币经历了恶性通货膨胀(en.wikipedia.org/wiki/Zimbabwean_dollar#Exchange_rate_history),以至于即使使用双打的系统也无法应对正在使用的大值。所以使用 int、long int 等确实不是一种选择。
【解决方案4】:

如果使用基于小数的货币,我建议使用 long int 以最小面额存储货币(例如,美国货币是美分)。

非常重要:请务必根据实际包含的内容命名所有货币值。 (例如:account_balance_cents)这样可以避免很多问题。

(另一个出现这种情况的例子是百分比。当它实际上包含一个未乘以一百的比率时,切勿将值命名为“XXX_percent”。)

【讨论】:

    【解决方案5】:

    查看相对较新的Intelr Decimal Floating-Point Math Library。它专门用于金融应用程序并实现了一些new standards for binary floating point arithmetic (IEEE 754r)

    【讨论】:

      【解决方案6】:

      GMP 库具有“bignum”实现,可用于处理金钱所需的任意大小的整数计算。请参阅mpz_class 的文档(警告:虽然这非常不完整,但提供了全范围的算术运算符)

      【讨论】:

        【解决方案7】:

        无论您决定使用哪种类型,我都建议您将其包装在“typedef”中,以便您可以在不同的时间进行更改。

        【讨论】:

        • 鉴于 typedef 只引入了一个别名并将您暴露给隐式数字转换,我会将其打包到一个类中。
        【解决方案8】:

        一种选择是将 10.01 美元存储为 1001,并以美分进行所有计算,在显示值时除以 100D。

        或者,使用浮点数,并且只在最后可能的时刻进行舍入。

        通常可以通过更改操作顺序来缓解问题。

        使用 (value * 10)/100 代替 value * .10 可获得 10% 的折扣,这将有很大帮助。 (记住 .1 是重复的二进制)

        【讨论】:

        • 永远不要使用浮点数。尝试将 0.60 美元表示为浮动。财务代码(银行的 AKA 代码)不允许有舍入错误 => 没有浮点数。
        • 0.6 不能存储为浮点数或双精度数。大多数实数不可能,浮点只是近似值。这是我得到的几个数字(0.6 和 8.075)的输出: float: 0.60000002384185791000 float: 8.07499980926513670000 double: 0.59999999999999998000 double: 8.0749999999999930000
        【解决方案9】:

        整数,总是——将它存储为美分(或任何你正在编程的最低货币。问题是,无论你用浮点数做什么,总有一天你会发现计算会有所不同的情况如果你用浮点数来做。最后一分钟的四舍五入不是答案,因为实际货币计算是在进行时四舍五入的。

        你也不能通过改变操作的顺序来避免这个问题——当你有一个百分比让你没有正确的二进制表示时,这会失败。如果你差一分钱,会计师会吓坏的。

        【讨论】:

          【解决方案10】:

          我们的金融机构使用“double”。由于我们是一家“固定收入”商店,因此我们有许多令人讨厌的复杂算法,无论如何都使用 double。诀窍是确保您的最终用户演示不会超过 double 的精度。例如,当我们有一份总价值数万亿美元的交易清单时,我们必须确保不会因为四舍五入的问题而打印出垃圾。

          【讨论】:

            【解决方案11】:

            不要将其存储为美分,因为在快速乘以税收和利息时,您会累积错误。至少,保留额外的两位有效数字:$12.45 将存储为 124,500。如果你把它保存在一个有符号的 32 位整数中,你将有 200,000 美元可以使用(正数或负数)。如果您需要更大的数字或更精确的数字,带符号的 64 位整数可能会为您提供长时间所需的所有空间。

            将这个值包装在一个类中可能会有所帮助,为您提供一个创建这些值、对它们进行算术运算以及格式化它们以供显示的地方。这也将为您提供一个中心位置来携带它所存储的货币(美元、加元、欧元等)。

            【讨论】:

            • 您是如何获得 2,000,000 的?你可以在一个有符号的 32 位整数中存储多达 20 亿美分,约合 2000 万美元。将其减去 2 位数以获得更高的精度,您将获得大约 20 万美元。
            • 使用两个额外精度的数字,64 位整数到底能容纳多大?
            • 另外,我看到这篇文章很老了,它仍然反映了存储货币的最佳方式吗?还是在 c++14 和/或 boost 中添加了一些现在会更好的东西?
            • 相反。存储应该是美分,因为没有亚美分的钱​​。计算时,应注意使用适当的类型,并及时正确地四舍五入。
            • @einpoklum 美分最多只需要小数点后 2 位,但投资交易通常使用小数点后 4-6 位之间的值进行操作。因此,存储可能需要比美分提供的更高的精度。
            【解决方案12】:

            了解您的数据范围。

            浮点数仅适用于 6 到 7 位的精度,因此这意味着最大值约为 +-9999.99,无需四舍五入。对于大多数金融应用程序来说,它是无用的。

            双精度数适用于 13 位数字,因此:+-99,999,999,999.99,使用大数字时仍需小心。认识到减去两个相似的结果会剥夺大部分精度(有关潜在问题,请参阅有关数值分析的书)。

            32 位整数可以达到 +-20 亿(缩放到美分会减少 2 个小数位)

            64 位整数可以处理任何金钱,但同样,在转换和乘以应用中可能是浮点数/双倍数的各种费率时要小心。

            关键是要了解您的问题域。您对准确性有哪些法律要求?您将如何显示这些值?转换多久发生一次?你需要国际化吗?在做出决定之前,请确保您可以回答这些问题。

            【讨论】:

              【解决方案13】:

              继续写你自己的钱(http://junit.sourceforge.net/doc/testinfected/testing.htm)或货币()类(取决于你需要什么)。并测试它。

              【讨论】:

                【解决方案14】:

                最大的问题是四舍五入!

                42,50 欧元的 19% = 8,075 欧元。由于德国的四舍五入规则,这是 8,08 欧元。问题是,(至少在我的机器上)8,075 不能表示为双精度。即使我将调试器中的变量更改为此值,我最终也会得到 8,0749999....

                这是我的舍入函数(以及我能想到的任何其他浮点逻辑)失败的地方,因为它产生 8,07 欧元。有效数字为 4,因此该值向下舍入。这是完全错误的,除非您尽可能避免使用浮点值,否则您无能为力。

                如果您将 42,50 € 表示为整数 42500000,效果会很好。

                42500000 * 19 / 100 = 8075000。现在您可以应用 8080000 以上的舍入规则。出于显示原因,这可以很容易地转换为货币值。 8,08 欧元。

                但我总是把它放在一个班级里。

                【讨论】:

                  【解决方案15】:

                  你可以试试十进制数据类型:

                  https://github.com/vpiotr/decimal_for_cpp

                  旨在存储以货币为导向的值(货币余额、货币汇率、利率)、用户定义的精度。最多 19 位。

                  它是 C++ 的仅标头解决方案。

                  【讨论】:

                    【解决方案16】:

                    解决方案很简单,将所需的精度存储为移位整数。但是当读入转换为双浮点数时,计算会遭受更少的舍入错误。然后在数据库中存储时乘以所需的任何整数精度,但在截断为整数之前添加 +/- 1/10 以补偿截断错误,或 +/- 51/100 进行舍入。 轻松愉快。

                    【讨论】:

                      【解决方案17】:

                      将美元和美分金额存储为两个单独的整数。

                      【讨论】:

                      • 为什么投反对票?这就是一些主要金融机构存储价格的方式。 ;(
                      【解决方案18】:

                      对于 32 位,我会使用有符号长整数,对于 64 位,我会使用有符号长整数。这将为您提供基础数量本身的最大存储容量。然后我会开发两个自定义操纵器。一种是根据汇率转换该数量,另一种是将该数量格式化为您选择的货币。您可以为各种金融操作/规则开发更多的操纵器。

                      【讨论】:

                        【解决方案19】:

                        您说您查看了 boost 库,但没有发现任何内容。 但是你有multiprecision/cpp_dec_float,上面写着:

                        这种类型的基数是 10。因此,它的行为可能与 base-2 类型略有不同。

                        因此,如果您已经在使用 Boost,这应该对货币价值和操作有好处,因为它的基数为 10 数字和 50 或 100 位精度(很多)。

                        见:

                        #include <iostream>
                        #include <iomanip>
                        #include <boost/multiprecision/cpp_dec_float.hpp>
                        
                        int main()
                        {
                            float bogus = 1.0 / 3.0;
                            boost::multiprecision::cpp_dec_float_50 correct = 1.0 / 3.0;
                        
                            std::cout << std::setprecision(16) << std::fixed 
                                      << "float: " << bogus << std::endl
                                      << "cpp_dec_float: " << correct << std::endl;
                                  
                            return 0;
                        }
                        

                        输出:

                        浮点数:0.3333333432674408

                        cpp_dec_float: 0.3333333333333333

                        *我并不是说浮点数(以 2 为底)不好,而十进制(以 10 为底)好。他们只是表现不同......

                        ** 我知道这是一篇旧帖子,并且 boost::multiprecision 是在 2013 年推出的,所以想在这里评论一下。

                        【讨论】:

                          【解决方案20】:

                          这是一个非常古老的帖子,但我想我会更新一点,因为它已经有一段时间了,事情已经发生了变化。我在下面发布了一些代码,这些代码代表了我能够使用long long integer 数据类型在C 编程语言中表示货币的最佳方式。

                          #include <stdio.h>
                          
                          int main()
                          {
                              // make BIG money from cents and dollars
                              signed long long int cents = 0;
                              signed long long int dollars = 0;
                          
                              // get the amount of cents
                              printf("Enter the amount of cents: ");
                              scanf("%lld", &cents);
                          
                              // get the amount of dollars
                              printf("Enter the amount of dollars: ");
                              scanf("%lld", &dollars);
                          
                              // calculate the amount of dollars
                              long long int totalDollars = dollars + (cents / 100);
                          
                              // calculate the amount of cents
                              long long int totalCents = cents % 100;
                          
                              // print the amount of dollars and cents
                              printf("The total amount is: %lld dollars and %lld cents\n", totalDollars, totalCents);
                          }
                          

                          【讨论】:

                            猜你喜欢
                            • 2011-10-19
                            • 2012-10-13
                            • 1970-01-01
                            • 2010-10-11
                            • 1970-01-01
                            相关资源
                            最近更新 更多