如何在Delphi中比较货币值时避免舍入问题?

4

据我所知,Delphi Win32中的货币类型取决于处理器浮点精度。因此,在比较两个货币值时,我遇到了舍入问题,根据机器不同返回不同的结果。

目前,我正在使用SameValue函数,并传递Epsilon参数= 0.009,因为我只需要2位小数精度。

有没有更好的方法来避免这个问题?

6个回答

15

在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.

你正在使用的第三方库可能会将控制字设置为不同的值。在重要计算的起始点明确地设置控制字(即舍入模式)可能是有必要的。

此外,如果你的计算需要转换为普通浮点数然后再转换回货币类型,那么一切都无法保证 - 审计起来太难了。确保所有计算都是以货币类型进行的。


请注意,货币类型之间的算术运算是使用浮点指令完成的。由于浮点二进制表示法,结果容易受到舍入误差的影响。 - LU RD

12

不,货币类型不是浮点类型。它是采用整数存储实现的固定精度十进制数,可以进行准确比较,并且不会像双精度型(Double)一样存在四舍五入问题。因此,如果您在Currency变量中看到不精确的值,则问题不在于Currency类型本身,而是您将什么放入其中。很可能,在代码的其他地方进行了浮点计算。由于您未显示该代码,因此很难在这个问题上提供更多帮助。但一般来说,解决方案将是在存储在Currency变量之前将浮点数舍入到正确的精度,而不是对Currency变量进行不精确的比较。


2
从Delphi帮助文档中:货币类型:一个8字节(64位)的货币数以缩放和带符号的64位整数形式存储,其中四个最低有效数字隐含地表示四个小数位。 - malach
1
在FPU中,两个货币值的乘法是通过浮点指令完成的。结果容易受到舍入误差的影响。 - LU RD

2
更快、更安全的比较两个货币值的方法是将变量映射到它们内部的Int64表示形式:
function CompCurrency(var A,B: currency): Int64;
var A64: Int64 absolute A;
    B64: Int64 absolute B;
begin
  result := A64-B64;
end;

这样做可以避免在比较时出现任何舍入误差(使用*10000整数值),并且比默认的基于FPU的实现更快(特别是在64位XE2编译器下)。有关更多信息,请参见此文章

0

如果你的情况和我类似,你可能会觉得这种方法很有帮助。我主要从事工资核算工作。假设一个企业有3个部门,并且希望将员工的成本均匀分摊到这三个部门中,很多时候都会出现舍入问题。

我的做法是通过循环遍历每个部门,使每个部门承担总成本的三分之一,并将所承担的成本累加到一个小计(货币)变量中。但是当循环变量等于上限时,我不再乘以该比例,而是从总成本中减去小计变量的值,并将差额放在最后一个部门中。由于这个过程产生的会计分录总是需要平衡,我相信这种方法一直有效。


0

查看线程:

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会修改控制字。


-2

为避免 Delphi 中可能出现的货币舍入问题,请使用 4 位小数。

这将确保在处理非常小的金额时,您永远不会遇到舍入问题。

"曾经历过。已完成。编写了单元测试。"


1
哇,我真的写了这个。 现在很想删掉它,但它可以作为不应该做的例子。 ;) - Matt Lacey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接