德尔福错误的双精度计算



我在使用双精度变量计算一个简单的算术方程时遇到问题。

我有一个组件,它的属性Value是双精度的,我将此属性设置为100。

然后我做一个简单的减法来检查这个值是否真的是100:

var
check: double;
begin
  check:= 100 - MyComponent.Value
  showmessage(floattostr(check));
end;

问题是我没有得到零,我得到了-1.4210854715202E-14,这是一个问题,因为我的程序检查这个结果是否完全是零

知道怎么解决吗?

尽管您另有声明,但MyComponent.Value返回的值显然不完全等于100。如果是,则100 - MyComponent.Value将恰好等于0。我们可以这么说,因为100在二进制浮点中是精确可表示的。

很容易看到100.0 - 100.0 = 0.0

var
  x, y: Double;
....
x := 100.0;
y := 100.0;
Assert(x-y=0.0);

在你的场景中,你会发现

MyComponent.Value = 100.0

计算为CCD_ 7。

一般来说,试图准确比较浮点值总是一件危险的事情。特别是如果这些值是算术运算的结果,那么浮点运算固有的不精确性将意味着精确的比较通常不会给出您期望的结果。

我假设MyComponent.Value实际上执行算术运算,而不是像您所说的那样返回100.0

有时,检查浮点值是否相等的最佳方法是检查近似相等。例如,abs(x-y)<tol,其中tol是一些小数字。这样做的困难在于,很难找到一个稳健的tol选择。

如果不了解更多细节,很难说你应该如何实现这个测试。

使用浮点数时,不应执行精确比较;始终使用一个小的Epsilon值,如《浮点指南-每个程序员都应该知道的》中所述。

注:我特意在绝对意义上说明了这一点。当然有是例外,但在实践中,这些通常是例外。属于当然,你可能处于这样的情况:你的问题域例外,并保证进行精确比较。在绝大多数代码中事实并非如此。

Math单元(它已经使用Delphi很长一段时间了)包含以下为您处理Epsilon值的函数。

当您将零的Epsilon(即0.0)或根本没有值传递给下面的函数时,它们将使用这些常数来估计一个合理的值。

注:

要使用的Epsilon的适当值取决于你使用的计算:有时不准确会累积到很多大于这些常数的值。

const
  FuzzFactor = 1000;
  SingleResolution   = 1E-7 * FuzzFactor;
  DoubleResolution   = 1E-15 * FuzzFactor;
{$IFDEF EXTENDEDIS10BYTES}
  ExtendedResolution = 1E-19 * FuzzFactor;
{$ELSE  EXTENDEDIS10BYTES}
  ExtendedResolution = DoubleResolution;
{$ENDIF EXTENDEDIS10BYTES}

功能:

function CompareValue(const A, B: Extended; Epsilon: Extended = 0): TValueRelationship; overload;
function CompareValue(const A, B: Double; Epsilon: Double = 0): TValueRelationship; overload;
function CompareValue(const A, B: Single; Epsilon: Single = 0): TValueRelationship; overload;
function SameValue(const A, B: Extended; Epsilon: Extended = 0): Boolean; overload;
function SameValue(const A, B: Double; Epsilon: Double = 0): Boolean; overload;
function SameValue(const A, B: Single; Epsilon: Single = 0): Boolean; overload;
function IsZero(const A: Extended; Epsilon: Extended = 0): Boolean; overload;
function IsZero(const A: Double; Epsilon: Double = 0): Boolean; overload;
function IsZero(const A: Single; Epsilon: Single = 0): Boolean; overload;

建议当其中一个数字是浮点时,不要通过简单的减法来检查零。相反,对于Epsilon参数,可以使用任意精度(例如0.00001)的IsZero函数。

procedure CheckFor100(MyPrecision);
begin
  if IsZero(100 - MyComponent.Value, MyPrecision) then
    ShowMessage('MyComponent value was 100')
  else
    ShowMessage('MyComponent value was not 100');
end;

或者,您可能希望考虑SameValue函数。我认为IsZero和SameValue都在数学单元中。

最新更新