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

Tro*_*ian 5 delphi delphi-xe2

记下以下记录:

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;
Run Code Online (Sandbox Code Playgroud)

使用定义为的方法:

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;
Run Code Online (Sandbox Code Playgroud)

和常数:

  const
    jHat2D : TVector2D = (x: 0; y: 1);
Run Code Online (Sandbox Code Playgroud)

我期望Boolean的值(jHat2D = TVector2D.New(0,0.707).Normalised)True.然而它出来了False.

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

http://i.imgur.com/9XhQqgD.png

它不能,这正是1的情况下,否则Boolean价值(jHat2D = TVector2D.New(0,0.707).Normalised)True.

有任何想法吗?

编辑

AccuracyType定义为:Accuracy = Double

Del*_*ics 8

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

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

在断点处你会看到:

v1.y      = 1
v2.y      = 0.999999999999999889
Run Code Online (Sandbox Code Playgroud)

(whosrdaddy在问题的评论中提供了上述简短版本,但我保留了我的调查的长期形式 - 见结论后面的行- 因为它可能在其他类似的情况下以及潜在的兴趣中证明是有用的)

结论

虽然调试器可视化严格地说是不正确的(或者最好是误导性的),但它们从来都不是非常正确的.:)

那么问题是你是否要求在一定的公差范围内具有严格的准确性或准确性.如果是后者,那么您可以采用SameValue(),并使用适合您所需精度的EPSILON.

否则,您必须接受在调试代码时,您不能依赖调试器来代表调试中涉及的值,以达到代码本身所依赖的准确度.

选项:自定义调试可视化

或者,您可能希望研究为TVector2D类型创建自定义调试可视化,以将x/y值表示为代码中使用的准确度.

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

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

// Yields  0.999999999999999889
Run Code Online (Sandbox Code Playgroud)

长期原始调查

我修改了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;
Run Code Online (Sandbox Code Playgroud)

通过在调试器设置手表提供了内存转储AY ^通过^我们可以看到,这两个值在内部表示非常不同:

v1.y   : $3f f0 00 00 00 00 00 00
v2.y   : $3f ef ff ff ff ff ff ff
Run Code Online (Sandbox Code Playgroud)

注意:由于英特尔的Little Endian特性,与上述实际值相比,监视值结果中的字节顺序相反.

然后我们可以通过将带有这些内部表示的Doubles传递给FloatToStr()来测试假设:

var
  a: Double;
  b: Double;
  ai: Int64 absolute a;
  bi: Int64 absolute b;

begin
  ai := $3ff0000000000000;
  bi := $3fefffffffffffff;

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

  // Yields 's' = '1 = 1';
end;
Run Code Online (Sandbox Code Playgroud)

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

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

uses
  Math;

const
  EPSILON = 0.1;

B := SameValue(V1.y, V2.y, EPSILON);
Run Code Online (Sandbox Code Playgroud)

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

EPSILON = 0.000000000000001;   // Yields B = TRUE
EPSILON = 0.0000000000000001;  // Yields B = FALSE
Run Code Online (Sandbox Code Playgroud)


who*_*ddy 6

您的问题源于这样的事实:2个浮点值不是100%相等,并且Debug Inspector对浮点进行舍入,以查看您需要添加监视并将浮点指定为可视化器的实际值:

在此输入图像描述

在此输入图像描述

使用内存转储可视化工具还可以显示2个值之间的差异:

在此输入图像描述