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

时间:2015-04-30 16:14:34

标签: 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;

使用定义为:

的方法
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);

我希望Boolean的{​​{1}}值为(jHat2D = TVector2D.New(0,0.707).Normalised)。然而它出现为True

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

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

情况不一定是1,否则1的{​​{1}}值将是Boolean

有什么想法吗?

修改

(jHat2D = TVector2D.New(0,0.707).Normalised)True,定义为:Accuracy

2 个答案:

答案 0 :(得分:8)

假设准确性 Double 类型的同义词,这是调试器对浮点值进行可视化的错误。由于浮点内部表示的固有问题, v1.Y v2.Y 非常轻微不同的值,尽管两者都接近1。

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

在断点处你会看到:

v1.y      = 1
v2.y      = 0.999999999999999889

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

结论

虽然调试器可视化严格地说是不正确的(或者最好是误导性的),但它们永远不会非常接近正确。 :)

接下来的问题是,您是否需要严格准确度或准确度以达到一定的容差。如果是后者,那么您可以采用 SameValue(),并根据您所需的准确度定义 EPSILON

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

选项:自定义调试可视化

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

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

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

// Yields  0.999999999999999889

原始调查的长版

我修改了等于操作符,以便我可以检查两个值 v1.y v2.y 的内部表示形式:< / p>

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 ^ 内存转储 ,我们看到这两个值在内部表示非常不同:

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

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

然后我们可以通过将双打与这些内部表示传递到 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;

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

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

uses
  Math;

const
  EPSILON = 0.1;

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

通过逐步减少 EPSILON 的价值,我们发现 v1.y v2.y 的差异小于 0.000000000000001 自:

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

答案 1 :(得分:6)

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

enter image description here

enter image description here

使用内存转储可视化工具也会显示两个值之间的差异:

enter image description here