【问题标题】:Delphi wrong double precision calculationDelphi错误的双精度计算
【发布时间】:2013-07-27 17:00:17
【问题描述】:

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

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

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

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

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

知道怎么解决吗?

【问题讨论】:

标签: delphi floating-point


【解决方案1】:

尽管您另有声明,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

评估为False

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

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

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

如果不知道更多细节,很难说你应该如何实施这个测试。

【讨论】:

  • MyComponent.Value 的值肯定是 100。我还使用以下方法打印了这个值:showmessage(FormatFloat('#.#######E-##', MyComponent.Value) .我得到了1E2。但是当我做减法时,我得到了别的东西......但是你的宽容想法是一个很好的想法,我也想过,但我想了解为什么会发生这种情况以及是否有更聪明的方法解决这个问题的方法,但考虑到浮点运算的不确定性,我想我应该使用这个容差思想
  • 没有。 100% 肯定,值不是 100。如果是,MyComponent.Value=100 将评估为 True。它不会。当您使用 FormatFloat 时,您四舍五入到少量有效数字。
  • 如果你还是不信,试试这个:ShowMessage(FormatFloat('#.#######E-##', 99.999999999))
  • @DavidHeffernan 您是对的,MyComponent.Value 返回的值不是 100.0,但您在最后一条评论中的陈述仅提供了为什么非 100.0 值打印为 100.0 的一半解释。毕竟,双精度 99.9999999999999999 打印为 100.0,因为它 100.0。作为结论,您的语句应该打印 99.999999999 和更多的十进制数字,以表明它实际上不是 100.0。
【解决方案2】:

当使用浮点数时,你不应该执行精确比较;始终使用较小的 Epsilon 值,如 The Floating-Point Guide - What Every Programmer Should Know 中所述。

注意:我故意以绝对的方式陈述这一点。当然有 是例外,但实际上这些通常是例外。的 当然,您可能会遇到问题域是 例外,并保证精确比较。在绝大多数代码中 我一直坚持,事实并非如此。

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;

【讨论】:

  • 很多时候很容易精确比较浮点值。有时这是一个坏主意。没有你说的那么清楚。
  • 在我的场景中,我将用户输入的数据与其他用户输入的数据进行比较。所以我可能有一个属性设置器,我会写if NewValue<>OldValue。或者我可能有记忆或缓存优化,输入参数的精确比较将是恰当的。
  • might 有点模糊。当您在精确比较和基于 epsilon 的比较之间进行选择时,您是否有更具体的标准?
  • 在精确是正确选择的情况下,我会使用精确比较。我不认为你会得到一套具体的规则。每个案件都根据其本身的案情进行判断。
【解决方案3】:

当其中一个数字是浮点数时,建议您不要通过简单的减法检查是否为零。相反,对 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 都在数学单元中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-06-06
    • 1970-01-01
    • 2014-02-21
    • 1970-01-01
    • 2013-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多