据我所知,Delphi Win32中的货币类型取决于处理器浮点精度。因此,在比较两个货币值时,我遇到了舍入问题,根据机器不同返回不同的结果。
目前,我正在使用SameValue函数,并传递Epsilon参数= 0.009,因为我只需要2位小数精度。
有没有更好的方法来避免这个问题?
据我所知,Delphi Win32中的货币类型取决于处理器浮点精度。因此,在比较两个货币值时,我遇到了舍入问题,根据机器不同返回不同的结果。
目前,我正在使用SameValue函数,并传递Epsilon参数= 0.009,因为我只需要2位小数精度。
有没有更好的方法来避免这个问题?
在Delphi中,货币类型是一个64位整数,乘以1/10,000作为比例;换句话说,它的最小增量相当于0.0001。与浮点代码相比,它不容易受到精度问题的影响。
但是,如果您将货币数字乘以浮点类型,或者除以货币值,舍入就需要以某种方式解决。FPU控制此机制(称为“控制字”)。Math单元包含一些控制此机制的过程,特别是SetRoundMode。您可以在此程序中看到其效果:
{$APPTYPE CONSOLE}
uses Math;
var
x: Currency;
y: Currency;
begin
SetRoundMode(rmTruncate);
x := 1;
x := x / 6;
SetRoundMode(rmNearest);
y := 1;
y := y / 6;
Writeln(x = y); // false
Writeln(x - y); // 0.0001; i.e. 0.1666 vs 0.1667
end.
你正在使用的第三方库可能会将控制字设置为不同的值。在重要计算的起始点明确地设置控制字(即舍入模式)可能是有必要的。
此外,如果你的计算需要转换为普通浮点数然后再转换回货币类型,那么一切都无法保证 - 审计起来太难了。确保所有计算都是以货币类型进行的。
不,货币类型不是浮点类型。它是采用整数存储实现的固定精度十进制数,可以进行准确比较,并且不会像双精度型(Double)一样存在四舍五入问题。因此,如果您在Currency变量中看到不精确的值,则问题不在于Currency类型本身,而是您将什么放入其中。很可能,在代码的其他地方进行了浮点计算。由于您未显示该代码,因此很难在这个问题上提供更多帮助。但一般来说,解决方案将是在存储在Currency变量之前将浮点数舍入到正确的精度,而不是对Currency变量进行不精确的比较。
function CompCurrency(var A,B: currency): Int64;
var A64: Int64 absolute A;
B64: Int64 absolute B;
begin
result := A64-B64;
end;
如果你的情况和我类似,你可能会觉得这种方法很有帮助。我主要从事工资核算工作。假设一个企业有3个部门,并且希望将员工的成本均匀分摊到这三个部门中,很多时候都会出现舍入问题。
我的做法是通过循环遍历每个部门,使每个部门承担总成本的三分之一,并将所承担的成本累加到一个小计(货币)变量中。但是当循环变量等于上限时,我不再乘以该比例,而是从总成本中减去小计变量的值,并将差额放在最后一个部门中。由于这个过程产生的会计分录总是需要平衡,我相信这种方法一直有效。
查看线程:
D7 / DUnit:所有的CheckEquals(Currency, Currency)测试突然失败...
https://forums.codegear.com/thread.jspa?threadID=16288
看起来我们开发工作站上的更改导致货币比较失败。我们还没有找到根本原因,但在两台运行Windows 2000 SP4的计算机上,无论是gds32.dll(InterBase 7.5.1或2007)和Delphi(7和2009)的版本如何,这一行代码
TIBDataBase.Create(nil);
将控制字的值从$1372更改为$1272。
现在,所有单元测试中的货币比较都会失败,并显示有趣的消息。
Expected: <12.34> - Found: <12.34>
gds32.dll文件未被修改,因此我猜测该库存在对第三方dll的依赖关系,该dll会修改控制字。
为避免 Delphi 中可能出现的货币舍入问题,请使用 4 位小数。
这将确保在处理非常小的金额时,您永远不会遇到舍入问题。
"曾经历过。已完成。编写了单元测试。"