TL; DR1 在C中代表货币或货币的准确和可维护的方法是什么?
问题的背景:
许多其他语言已经回答了这个问题,但我找不到C语言的可靠答案。
注意:对于其他语言,还有更多类似的问题,我只是为了代表性目的而提出了一些问题。
所有这些问题都可以归结为“使用a decimal
数据类型“特定类型可能因语言而异。
有一个 相关问题 最终建议使用“固定点”方法,但没有一个答案在C中使用特定数据类型。
同样,我查看过任意精度库,例如 GMP但是我不清楚这是否是最好的使用方法。
简化假设:
假设基于x86或x64的架构,但请调出任何会影响基于RISC的架构的假设,例如Power芯片或Arm芯片。
计算的准确性是主要要求。 易于维护 将是下一个要求。计算速度很重要,但与其他要求相比是第三位的。
计算需要能够安全地支持准确的操作 磨 以及数万亿(10 ^ 9)的支持值
与其他问题的区别:
如上所述,此类问题之前已被问过多种其他语言。由于几个原因,这个问题与其他问题不同。
使用接受的答案: 为什么不使用Double或Float来表示货币?,让我们强调差异。
(解决方案1一种几乎适用于任何语言的解决方案是使用整数,并计算美分。例如,1025将是10.25美元。几种语言也有内置类型来处理钱。 (解决方案2)其中,Java有BigDecimal类,C#有小数类型。
重点强调了两个建议的解决方案
第一种解决方案基本上是“定点”方法的变体。该解决方案存在一个问题,即建议的范围(跟踪分数)不足以进行基于轧机的计算,并且在舍入时将丢失重要信息。
另一种解决方案是使用原生 decimal
C中没有的课程
同样,答案不考虑其他选项,例如创建用于处理这些计算的结构或使用任意精度库。这些是可以理解的差异,因为Java没有结构,为什么在语言中有本机支持时考虑第三方库。
此问题与该问题和其他相关问题不同,因为C不具有相同级别的本机类型支持,并且具有其他语言不具备的语言功能。我还没有看到任何其他问题解决在C中可以采用的多种方式。
问题:
从我的研究来看,似乎是这样 float
由于浮点错误,用于表示C程序中的货币的数据类型不合适。
我应该用什么来代表C中的钱,为什么这种方法比其他方法更好?
1 这个问题以较短的形式开始,但收到的反馈意见表明需要澄清问题。
使用整数数据类型(long long,long,int)或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;
}
一个 变化 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));
我的水晶球说到2022年,美国将下降0.01美元,最小的单位将是0.05美元。我会使用最能处理这种转变的方法。
如果速度是您最关心的问题,那么请使用缩放到您需要表示的最小单位的整数类型(例如a 磨,这是0.001美元或0.1美分)。从而, 123456
代表 $123.456
。
这种方法的问题是你可能会用完数字; 32位无符号整数可以表示10个十进制数字,因此您可以在此方案下表示的最大值 $9,999,999.999
。如果您需要处理数十亿的价值,那就不好了。
另一种方法是使用具有一个整数成员的结构类型来表示整个美元金额,并使用另一个整数成员来表示小数金额(再次,按比例缩放到您需要表示的最小单位,无论是美分,工厂还是其他小的),类似的 timeval
struct在一个字段中保存整秒,在另一个字段中保存纳秒:
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位,它远远超出了美国所有部门 结合。
在进行计算时你需要注意一些问题,所以你不要将有效数字的一半分开。
这是一个知道范围的游戏,以及分割后的舍入是好的。
例如,在除法后进行适当的舍入(.5 up variaty)可以通过首先将一半的分子加到该值然后进行除法来完成。虽然如果您正在进行融资,但您需要更高级的圆形系统,但需要您的会计师批准。
long long res = (amount * interest + 500)/1000;
只有在与用户沟通时才能转换为美元(或其他)。