如何在C中表示货币或货币

How to represent currency or money in C

TL;DR1用C表示货币或货币的准确和可维护的方法是什么?

问题背景:许多其他语言都回答了这个问题,但我找不到C语言的可靠答案。

  • C我应该使用什么数据类型来表示C中的货币?

  • 为什么不使用双元或浮点来表示货币?

  • 目标C如何在目标C/IOS中表示资金?

注意:对于其他语言,还有很多类似的问题,我只是为了表示的目的拉了几个问题。

所有这些问题都可以归结为"使用decimal数据类型",其中特定类型可能因语言而异。

有一个相关的问题最终建议使用"定点"方法,但没有一个答案使用C中的特定数据类型。

同样,我也研究过任意精度库,比如gmp,但我不清楚这是否是最好的使用方法。

简化假设:

  • 假设一个基于x86或X64的体系结构,但是请说出任何可能影响基于RISC的体系结构的假设,例如电源芯片或ARM芯片。

  • 计算的准确性是首要要求。下一个要求是易于维护。计算速度很重要,但却是其他要求的第三级。

  • 计算需要能够安全地支持精确到磨机的操作,以及支持高达万亿(10^9)的值。

与其他问题的区别:

如上所述,这种类型的问题之前已经被多个其他语言问过。这个问题与其他问题有两个不同的原因。

使用接受的答案:为什么不使用double或float来表示货币?,让我们突出差异。

(Solution 1) A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. (Solution 2) Among others, Java has the BigDecimal class, and C# has the decimal type.

emphasis added to highlight the two suggested solutions

第一个解决方案本质上是"定点"方法的变体。该解决方案存在一个问题,即建议的范围(跟踪美分)不足以进行基于工厂的计算,并且在舍入时会丢失重要信息。

另一种解决方案是使用C中不可用的本地decimal类。

同样,答案不考虑其他选项,例如创建用于处理这些计算的结构或使用任意精度库。这些差别是可以理解的,因为Java没有结构,当语言中有本地支持时,为什么要考虑第三方库。

这个问题不同于那个问题和其他相关的问题,因为C没有与其他语言相同级别的本机类型支持,并且具有其他语言不具备的语言功能。我还没有看到其他任何问题解决了C中解决这个问题的多种方法。

问题:根据我的研究,由于浮点错误,float似乎不是用于表示C程序中货币的适当数据类型。

我应该用什么来表示C中的钱,为什么这种方法比其他方法更好?

1此问题以较短的形式开始,但收到的反馈表明需要澄清问题。


使用整数数据类型(long long、long、int)或BCD(二进制编码十进制)算术库。您应该存储将要显示的最小数量的十分之一或百分之一。也就是说,如果您使用美元并表示美分(百分之一美元),则您的数值应为表示mill或millrays(十分之一或百分之一美分)的整数。额外的重要数字将确保您的兴趣和类似的计算一致。

如果使用整数类型,请确保它的范围足够大,可以处理大量的问题。


最好的货币/货币表示法是使用更高精度的浮点类型,如double,它具有FLT_RADIX == 10。这些平台/编译器很少,因为大多数系统都有FLT_RADIX == 2

四种可选方案:整数、非十进制浮点、特殊十进制浮点、用户定义的结构。

整数:一个通用的解决方案使用最小面额的整数计数。例如,计算美元而不是美元。整数的范围需要合理的宽。像long long而不是int,因为int可能只能处理大约+/-320.00美元。这对于简单的会计任务(包括加减/多倍)很有用,但在利息计算中开始使用除法和复杂函数。每月付款公式。有符号整数数学没有溢出保护。四舍五入除法结果时需要注意。q = (a + b/2)/b不够好。

二元浮点:2个常见的陷阱:1)使用float,这通常精度不够,2)舍入错误。使用double井可以解决许多会计限额的问题1。然而,为了获得令人满意的结果,代码仍然需要使用一个期望的最小货币单位。

1
2
3
4
// Sample - does not properly meet nuanced corner cases.
double RoundToNearestCents(double dollar) {
  return round(dollar * 100.0)/100.0;
}

double的变化是使用最小单位(0.01或0.001)的double量。一个重要的优势是能够简单地使用round()函数进行取整,该函数本身可以满足角点情况。

一些系统提供了一个"十进制"类型,而不是double,满足小数64或类似的要求。尽管这处理了上述大多数问题,但却牺牲了可移植性。

当然,用户定义的结构(比如固定点)可以解决所有问题,除了代码太多容易出错和工作(相反)之外。结果可能功能完美,但缺乏性能。

结论:这是一个深刻的主题,每一种方法都值得更广泛的讨论。一般的答案是:没有一般的解决方案,因为所有的方法都有明显的弱点。所以这取决于应用程序的具体情况。

[编辑]考虑到OP的额外编辑,建议使用最小货币单位的double个数(例如:$0.01->double money = 1.0;)。在代码中的不同点,只要需要精确的值,就使用round()

1
2
double interest_in_cents = round(
    Monthly_payment(0.07/12 /* percent */, N_payments, principal_in_cents));

我的水晶球说,到2022年,美国将下降0.01美元,最小的单位将是0.05美元。我会使用最能处理这种转变的方法。


如果速度是你最关心的问题,那么使用一个积分型的单位,你需要表示的最小单位(如一个工厂,这是0.001美元或0.1美分)。因此,123456代表$123.456

这种方法的问题是可能会用完数字;32位无符号int可以表示10位十进制数字,因此在这种方案下可以表示的最大值是$9,999,999.999。如果你需要处理数十亿的价值,那就不好了。

另一种方法是使用一个结构类型,其中一个整数成员表示整美元金额,另一个整数成员表示小数美元金额(同样,缩放到需要表示的最小单位,无论是美分、米尔斯或更小的单位),类似于timeval结构,它在另一个是E场和纳秒:

1
2
3
4
struct money {
  long whole_dollars; // long long if you have it and you need it
  int frac_dollar;
};

一个int的宽度足以应付任何理智的人使用的缩放。如果whole_dollars部分为0,则保留签名。

如果您更担心存储任意大的值,那么总是有bcd,它可以表示比任何本机整数或浮点类型多得多的数字。

不过,表示法只占战斗的一半;您还必须能够对这些类型执行算术运算,并且对货币的操作可能有非常具体的舍入规则。所以在决定你的代理权时,你要考虑到这一点。


int(32或64,根据需要),并根据需要用分或部分分来思考。32位,以美分计算,你可以在一个值中代表多达4000万美元。有了64位,它远远超出了所有美国部门的总和。

在进行计算时,你必须注意一些问题,这样你就不会把有效数的一半分开。

这是一个知道范围的游戏,当除法后的四舍五入很好的时候。

例如,在除法之后做一个适当的取整(0.5以上的变量),可以先将分子的一半加到值上,然后再进行除法。不过,如果你在做财务工作,你需要一个更先进的轮值制,尽管你的会计师会批准。

1
long long res = (amount * interest + 500)/1000;

仅在与用户通信时转换为美元(或任何其他货币)。