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

TL; DR 1在C中表示货币或货币的准确和可维护的方法是什么?


问题背景:
这已经被许多其他语言所回答,但我找不到C语言的可靠答案。

  • C# 应该用什么数据types来代表C#中的钱?

  • Java 为什么不使用Double或Float来表示货币?

  • Objective-C 如何在Objective-C / iOS中表示金钱?

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

所有这些问题可以被提炼成“使用decimal数据types”,其中具体types可以根据语言而变化。

有一个相关的问题最终提出使用“固定点”方法,但是没有一个答案在C中使用特定的数据types。

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


简化假设:

  • 假定基于x86或x64的体系结构,但请提出任何会影响基于RISC的体系结构(如Power芯片或Arm芯片)的假设。

  • 计算的准确性是主要的要求。 易于维护将是下一个要求。 计算速度是重要的,但是对其他要求来说是高等的。

  • 计算需要能够安全地支持对工厂的准确操作,并支持高达数万亿的值(10 ^ 9)


与其他问题的区别:

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

使用接受的答案: 为什么不使用Double或Float来表示货币? ,让我们强调一下差异。

解决scheme1 )以几乎任何语言工作的解决scheme是使用整数,并计数分。 例如,1025将是10.25美元。 几种语言也有内置的types来处理金钱。 ( 解决scheme2 )其中,Java有BigDecimal类,C#有十进制types。

重点强调了两个build议的解决scheme

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

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

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

这个问题不同于这个问题和其他相关的问题,因为C没有相同的本机types支持级别,并且具有其他语言不支持的语言function。 而且我还没有看到任何其他的问题可以解决C中可以用到的多种方法。


问题:
从我的研究来看,由于浮点错误, float似乎不是一个用于在C程序中表示货币的合适的数据types。

C应该用什么来代表金钱,为什么这种方法比其他方法更好呢?

1 这个问题起源较短,但收到的反馈意见表明有必要澄清这个问题。

使用整数数据types(long long,long,int)或BCD(二进制编码的十进制)算术库。 您应该存储您将显示的最小数量的十分之一或百分之一。 也就是说,如果您使用美元并提供美分(百分之一美元),则您的数值应该是代表磨坊或毫米(十分之一或百分之一)的整数。 额外的有效数字将确保您的兴趣和类似的计算一致。

如果使用整数types,请确保其范围足够大以处理所关注的数量。

如果速度是您最关心的问题,那么您可以使用一个整型缩放到您需要代表的最小单位(例如一个工厂 ,即0.001美元或0.1美分)。 因此, 123456代表$123.456

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

另一种方法是使用结构types,其中一个整数成员表示整个美元数量,另一个整数成员表示分数美元数量(再次,缩放到您需要表示的最小单位,无论是美分,铣刀还是其他更小),类似于在一个字段中保存整秒的timeval结构,在另一个字段中保存纳秒:

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

一个int值足够大,可以处理任何理智的人使用的缩放比例。 如果whole_dollars部分为0, whole_dollarswhole_dollars

如果您更担心存储任意大的值,那么始终存在BCD ,它可以表示比本地整型或浮点型更多的数字。

虽然代表只有一半的战斗, 您也必须能够对这些types进行算术运算,而对货币的操作可能会有非常具体的舍入规则 。 所以在决定你的代表性时,你会想要考虑到这一点。

最好的货币/货币表示法是使用更高的精度浮点types,例如具有FLT_RADIX == 10 double 。 这些平台/执行者是罕见的,因为绝大多数系统具有FLT_RADIX == 2

四种select:整数,非十进制浮点数,特殊的十进制浮点数,用户定义的结构。

整数 :一个常见的解决scheme使用所选币种中最小面额的整数。 以美分而不是美元为例。 整数范围需要合理宽。 类似long long而不是int int可能只能处理+/- $ 320.00。 这适用于简单的会计任务涉及加/减/多,但开始破解与利息计算中使用的分裂和复杂的function。 每月付款公式 。 有符号的整数math没有溢出保护。 舍入除法结果时需要注意。 q = (a + b/2)/b不够好。

二进制浮点数 :2个常见陷阱:1)使用float的精度不够,2)不正确的舍入。 使用double地址问题#1许多会计限制。 然而,代码仍然经常需要使用所需的最小货币单位来获得令人满意的结果。

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

double变化是使用最小单位的double (0.01或0.001)。 一个重要的优点是能够简单地通过使用round()函数本身满足angular落情况。

特殊的十进制浮点数有些系统提供了一个“decimal”types,而不是double ,它符合decimal64或类似的东西。 虽然这处理了上面的问题,但是可移植性却被牺牲了。

用户定义的结构 (如定点 )当然可以解决所有问题,除非它很容易出错,而且是工作 ( 取而代之 )。 结果可能完美,但性能不足。

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

[编辑]
鉴于OP的额外编辑,build议使用最小单位货币的double数(例如:$ 0.01 – > double money = 1.0; )。 在需要精确值的代码中的各个点上,使用round()

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

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

int (32或64,如你所需),并认为在美分或部分分需要。 有了32位和思考,你可以代表高达4000万美元的单一价值。 有了64位,它远远超过了所有美国部门的总和

在做计算的时候有一些小问题需要注意,所以你不能把一半的有效数字分开。

这是一个知道范围的游戏,什么时候划分后的四舍五入是好的。

例如,在分割之后进行适当的回合(最多五分之一)可以通过首先将一半的分子添加到该值然后进行分割来完成。 虽然如果你正在做财务,你将需要一个更先进的圆形系统,虽然你的会计师批准。

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

只有在与用户沟通时才转换为美元(或其他)。