TL; DR 1用C表示货币或货币的准确且可维护的方法是什么?
问题的背景:
许多其他语言已经回答了这个问题,但我找不到C语言的可靠答案.
Objective-C 如何在Objective-C/iOS中代表钱?
注意:对于其他语言,还有更多类似的问题,我只是为了代表性目的而提出了一些问题.
所有这些问题都可以提炼为"使用decimal数据类型",其中特定类型可能因语言而异.
有一个相关的问题最终建议使用"定点"方法,但没有一个答案使用C中的特定数据类型.
同样,我已经查看过任意精度库,例如GMP,但我不清楚这是否是最好的使用方法.
简化假设:
假设基于x86或x64的架构,但请调出任何可能影响基于RISC的架构(如Power芯片或Arm芯片)的假设.
计算的准确性是主要要求. 易于维护将是下一个要求.计算速度很重要,但与其他要求相比是三级的.
计算需要能够安全地支持对轧机精确的操作以及高达数万亿的支撑值(10 ^ 9)
与其他问题的区别:
如上所述,此类问题之前已被问过多种其他语言.由于几个原因,这个问题与其他问题不同.
使用接受的答案:为什么不使用Double或Float来表示货币?,让我们强调差异.
(解决方案1)几乎可以使用任何语言的解决方案是使用整数,并计算分数.例如,1025将是10.25美元.几种语言也有内置类型来处理钱.(解决方案2)其中,Java具有BigDecimal类,C#具有十进制类型.
重点强调了两个建议的解决方案
第一种解决方案基本上是"定点"方法的变体.该解决方案存在一个问题,即建议的范围(跟踪分数)不足以进行基于轧机的计算,并且在舍入时将丢失重要信息.
另一种解决方案是使用decimalC中不可用的本机类.
同样,答案不考虑其他选项,例如创建用于处理这些计算的结构或使用任意精度库.这些是可以理解的差异,因为Java没有结构,为什么在语言中有本机支持时考虑第三方库.
此问题与该问题和其他相关问题不同,因为C不具有相同级别的本机类型支持,并且具有其他语言不支持的语言功能.我还没有看到任何其他问题解决在C中可以采用的多种方式.
问题:
根据我的研究,float由于浮点错误,似乎不适合用于在C程序中表示货币的数据类型.
我应该用什么来代表C中的钱,为什么这种方法比其他方法更好?
1 此问题以较短的形式开始,但收到的反馈意见表明需要澄清问题.
使用整数数据类型(long long,long,int)或BCD(二进制编码的十进制)算术库.您应该存储将显示的最小量的十分之一或百分之一.也就是说,如果您使用美元并提供美分(百分之一美元),则您的数值应为表示毫克或毫克(十分之一或百分之一分)的整数.额外的重要数字将确保您的兴趣和类似的计算一致.
如果使用整数类型,请确保其范围足以处理所关注的数量.
如果速度是您最关心的问题,那么使用缩放到您需要表示的最小单位的整数类型(例如mill,即 0.001 美元或 0.1 美分)。因此,123456代表$123.456。
这种方法的问题在于您可能会用完数字;32 位 unsigned int 可以表示 10 位十进制数字,因此在此方案下您可以表示的最大值是$9,999,999.999. 如果您需要处理数十亿的值,那就不好了。
另一种方法是使用结构类型,其中一个整数成员表示整个美元金额,另一个整数成员表示小数美元金额(同样,缩放到您需要表示的最小单位,无论是美分、米尔斯还是其他单位)更小),类似于timeval在一个字段中保存整秒而在另一个字段中保存纳秒的结构:
struct money {
long whole_dollars; // long long if you have it and you need it
int frac_dollar;
};
Run Code Online (Sandbox Code Playgroud)
一个int是更比宽足以应付任何缩放理智的人会使用。如果该whole_dollars部分为 0,则对其进行签名。
如果您更担心存储任意大的值,那么总是有BCD,它可以表示比任何本机整数或浮点类型更多的数字。
不过,代表性只是成功的一半;您还必须能够对这些类型执行算术运算,并且对货币的运算可能具有非常具体的舍入规则。因此,在决定您的代表时,您需要考虑到这一点。
最好的货币/货币表示是使用较高的足够精度浮点型一样double有FLT_RADIX == 10.这些平台/编译器很少见,因为绝大多数系统都有FLT_RADIX == 2.
四种选择:整数,非十进制浮点,特殊十进制浮点,用户定义结构.
整数:常用解决方案使用所选货币中最小面额的整数计数.计算美分而非美元的示例.整数范围需要合理的范围.喜欢的东西long long,而不是int因为int只能处理大约+/- $ 320.00.这适用于涉及加/减/多次的简单会计任务,但是开始破解利息计算中使用的除法和复杂函数.每月付款公式.有符号整数数学没有溢出保护.舍入除法结果时需要小心谨慎. 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;
}
Run Code Online (Sandbox Code Playgroud)
甲上的变化double是使用double的最小单位(0.01或0.001)的量.一个重要的优点是能够简单地通过使用round()本身遇到极端情况的函数进行舍入.
特别十进制浮点 一些系统提供以外的"十进制"类型double,以满足 DECIMAL64或类似的东西.虽然这可以处理大多数问题,但是牺牲了可移植性.
用户定义的结构(如定点)当然可以解决所有问题,除了它很容易出错代码并且它是工作(相反).结果可能完美但缺乏性能.
结论这是一个深刻的主题,每种方法都值得进行更广泛的讨论.一般的答案是:没有通用的解决方案,因为所有方法都有明显的缺点.所以这取决于应用程序的细节.
[编辑]
鉴于OP的额外编辑,建议使用double最小单位货币的数量(例如:$ 0.01 - > double money = 1.0;).在需要精确值的代码中的各个点处,使用round().
double interest_in_cents = round(
Monthly_payment(0.07/12 /* percent */, N_payments, principal_in_cents));
Run Code Online (Sandbox Code Playgroud)
我的水晶球说到2022年,美国将下降0.01美元,最小的单位将是0.05美元.我会使用能够最好地处理这种转变的方法.
int(根据需要选择 32 或 64)并根据需要以分或部分分来考虑。使用 32 位并以美分计算,您可以用单个值表示高达 4000 万美元。凭借 64 位,它远远超过了美国所有部门的总和。
在进行计算时,您必须注意一些问题,这样您就不会除掉一半的有效数字。
这是一个了解范围以及除法后舍入何时合适的游戏。
例如,在除法之后进行适当的舍入(0.5 向上的变体)可以通过首先将分子的一半添加到该值然后进行除法来完成。不过,如果您从事财务工作,则需要经过会计师批准的更先进的循环系统。
long long res = (amount * interest + 500)/1000;
Run Code Online (Sandbox Code Playgroud)
仅在与用户沟通时转换为美元(或其他)。