Embarcadero RAD Studio XE2 调试器中显示的局部变量的准确性如何?显然 1 不等于 1

To what accuracy are the local variables displayed in the Embarcadero RAD Studio XE2 debugger? Apparently 1 is not equal to 1

记录如下:

TVector2D = record
  public
    class operator Equal(const V1, V2: TVector2D): Boolean;
    class operator Multiply(const D: Accuracy; const V: TVector2D): TVector2D;
    class operator Divide(const V: TVector2D; const D: Accuracy): TVector2D;
    class function New(const x, y: Accuracy): TVector2D; static;
    function Magnitude: Accuracy;
    function Normalised: TVector2D;
  public
    x, y: Accuracy;
  end;

方法定义为:

class operator TVector2D.Equal(const V1, V2: TVector2D): Boolean;
  var
    A, B: Boolean;
  begin
    Result := (V1.x = V2.x) and (V1.y = V2.y);
  end;

  class operator TVector2D.Multiply(const D: Accuracy; const V: TVector2D): TVector2D;
  begin
    Result.x := D*V.x;
    Result.y := D*V.y;
  end;

  class operator TVector2D.Divide(const V: TVector2D; const D: Accuracy): TVector2D;
  begin
    Result := (1.0/D)*V;
  end;

  class function TVector2D.New(const x, y: Accuracy): TVector2D;
  begin
    Result.x := x;
    Result.y := y;
  end;

  function TVector2D.Magnitude;
  begin
    RESULT := Sqrt(x*x + y*y);
  end;

  function TVector2D.Normalised: TVector2D;
  begin
    Result := Self/Magnitude;
  end;

和一个常量:

  const
    jHat2D : TVector2D = (x: 0; y: 1);

我希望 (jHat2D = TVector2D.New(0,0.707).Normalised)Boolean 值为 True。然而结果却是 False

在调试器中 TVector2D.New(0,0.707).Normalised.y 显示为 1

这不可能正好是 1,否则 (jHat2D = TVector2D.New(0,0.707).Normalised)Boolean 值将是 True

有什么想法吗?

编辑

Accuracy 是一个 Type 定义为:Accuracy = Double

假设 AccuracyDouble 类型的同义词,这是调试器浮点值可视化中的错误.由于浮点数内部表示的固有问题,v1.Yv2.Y略有 不同的值,但都近似于 1。

添加监视 v1.yv2.y。确保将这些监视值配置为表示 "Floating Point" 值,其中数字设置为 18 以获得最大细节。

在你的断点你会看到:

v1.y      = 1
v2.y      = 0.999999999999999889

(whosrdaddy 在问题的评论中提供了上面的简短版本,但我保留了我调查的长格式 - 请参阅 之后的行结论 - 因为它可能在其他类似情况下被证明是有用的,并且具有潜在的意义)

结论

虽然严格来说调试器可视化是不正确的(或者至多是误导),但它们仍然几乎正确。 :)

接下来的问题是,您是要求严格准确度还是在一定公差范围内的准确度。如果是后者,那么您可以采用 SameValue()EPSILON 定义适合您需要的准确度。

否则您必须接受,在调试代码时,您不能依赖调试器将调试中涉及的值表示为代码本身所依赖的准确度。

选项:自定义调试可视化本身

或者,您可能希望调查 creating a custom debug visualisation 您的 TVector2D 类型,以表示您的 x/y 值与代码中使用的准确性。

对于使用 FloatToStr() 的此类可视化,请使用 Format()%f 具有适当小数位数的格式说明符。例如下面的调用产生了通过观察上述变量获得的结果:

Format('%.18f', [v2.y]);

// Yields  0.999999999999999889

原始调查的长版

我修改了 Equal 运算符以允许我检查两个值 v1.yv2.y:

type
  PAccuracy = Accuracy;

class operator TVector2D.Equal(const V1, V2: TVector2D): Boolean;
var
  A, B: Boolean;
  ay, by: PAccuracy;
begin
  ay := @V1.y;
  by := @V2.y;

  A := (V1.x = V2.x);
  B := (V1.y = V2.y);

  result := A and B;
end;

通过在调试器中设置监视以提供 ay^by^[=96 的 Memory Dump =] 我们看到这两个值在内部表示非常不同:

v1.y   : f f0 00 00 00 00 00 00
v2.y   : f ef ff ff ff ff ff ff

注意: 由于 Intel 的 Little Endian 特性,与上述实际值相比,观察值结果中的字节顺序是相反的。

然后我们可以通过将具有这些内部表示的 Doubles 传递到 FloatToStr():

来检验假设
var
  a: Double;
  b: Double;
  ai: Int64 absolute a;
  bi: Int64 absolute b;

begin
  ai := ff0000000000000;
  bi := fefffffffffffff;

  s := FloatToStr(a) + ' = ' + FloatToStr(b);

  // Yields 's' = '1 = 1';
end;

因此我们可以得出结论,B的评价是正确的。 v1.yv2.y不同。调试器对 Double 值的表示不正确(或者充其量是误导)。

通过将 B 的表达式更改为使用 SameValue() 我们可以确定所涉及值之间的偏差:

uses
  Math;

const
  EPSILON = 0.1;

B := SameValue(V1.y, V2.y, EPSILON);

通过逐渐减小EPSILON的值,我们发现v1.yv2.y 相差小于 0.000000000000001 因为:

EPSILON = 0.000000000000001;   // Yields B = TRUE
EPSILON = 0.0000000000000001;  // Yields B = FALSE

您的问题源于 2 个浮点值并非 100% 相等并且调试检查器对浮点进行四舍五入,要查看您需要的实际值,您需要添加一个监视并将浮点指定为可视化工具:

使用内存转储可视化工具也揭示了 2 个值之间的差异: