【问题标题】:Is double/float equality guaranteed after copying in C#? [duplicate]在 C# 中复制后是否保证双精度/浮点相等? [复制]
【发布时间】:2019-03-27 23:03:29
【问题描述】:

对于一个非常简单的问题,我找不到一个清晰、简单的答案!我将把我的问题分成两个版本。

在 C# 中,假设我运行以下命令:

double x = 76239.78362194721;
double y = -3;
y = x;  // copied by value, as doubles are literals
//y = y + 1.4;  // Version 1 of my question has these commented-out
//x = x + 1.4;  // Version 2 of my question has these not commented-out
bool b = (x == y);

布尔值b 总是 是真的吗?也就是说,虽然双打之间的平等度量是不准确的,但计算机不会故意添加噪音吗?

b 能否取决于我问的是版本 1 还是版本 2? (我们可以假设 xy 接近 double 的最大值或最小值,也不接近 plus/minus epsilon。

谢谢! 〜格雷格

【问题讨论】:

  • 另请参阅Is floating point arithmetic stable,这是一个与您的问题更接近的重复问题。
  • 简而言之:any 双重计算可以在 any 64 位 或更高 的精度级别在 any 出于any 的原因,这可能会影响平等。 C# 只需要在分配给字段或数组元素时“四舍五入”到 64 位;本地人和临时人员可以随心所欲地进出 80 或 128 位精度。是的,这很糟糕,你应该把责任归咎于制造芯片的芯片设计者,因为这些芯片的计算成本更高。

标签: c# floating-point equality


【解决方案1】:

使用y = x; 复制不会导致任何精度损失。

赋值后x == y为真。

由于xy 相等,所以在

y = y + 1.4;
x = x + 1.4;

x == y 很可能是真的;然而,正如 Eric Lippert 所指出的(见下面的 cmets),这并不能保证。

始终以所需的精度比较双精度数:

const double Eps = 1e-10;
if (Math.Abs(x - y) < Eps) {
    // x and y are equal enough
} else {
    // x and y are not equal
}

当然,如果您只注释其中一个,xy 将相差大约 1.4。它们可能相差1.3999999999999991.400000000000001 左右。如果两者都很大(例如1.0e25),则加法将无效,因为它会影响被截断的小数。 double 的精度为 15-16 位。如果两者都非常小(例如1.0e-25),也是如此,因为它们的原始值将丢失,结果将是1.4

在 C# 交互窗口中,您可以对其进行测试

> 1e+25 + 1.4 == 1e+25
true

还有那个

> 1e-25 + 1.4 == 1.4
true

... 按值复制,因为双精度数是文字

嗯,不完全是。 System.Double(C# 别名double)是一个值类型3.141592654double 文字。在double x; 中,标识符xdouble 变量(或字段,如果它在类型级别声明)。

但即使你复制...

var p1 = new Person { Name = "John" }; // Where Person is a class.
var p2 = p1;
// Now, p1 and p2 reference the same object. I.e. they have the same value.

...变量的值是按值复制的。不同的是,这个值是一个参考。该值不是一个人对象,而是对它的引用。

p2.Name = "Mark";

现在p1.Name == "Mark" 为真,但p1 的值没有改变。它仍然是相同的参考。引用的对象确实发生了变化。


double d1 = 5;
double d2 = d1;
d2 = 77;

d1 仍然是 5。因为double 是值类型,所以变量中直接包含数字。不涉及引用。


在另一个示例中,引用是通过引用传递的

var p1 = new Person{ Name = "John" };
AssignAnotherPerson(ref p1);

void AssignAnotherPerson(ref Person p)
{
    p = new Person{ Name = "Sue" }; // Here p is an alias for p1.
}

调用此方法后,p1 将包含人员 Sue。

可以有四种情况: (1) 值类型按值传递。 (2) 通过引用传递的值类型。 (3) 按值传递的引用类型。 (4) 通过引用传递的引用类型。

【讨论】:

  • 请注意,如果 x 或 y 是 NAN,即使使用 x==x,== 运算符也会计算为 false。
  • 很遗憾,这个答案是错误的。 C# 不需要以相同级别的精度进行任何两个计算,这会影响相等性。
  • 此问题已作为重复问题关闭;有关详细信息,请参阅重复的问题。简短的版本是:浮点 registers 允许为 80 或 128 或任何位,但字段和堆栈槽保证仅为 64 位,并且 C# 允许将浮点数移入和移出寄存器然而,抖动感觉是最有效的。这意味着一个计算可以在 128 位算术中完成,而另一个计算可以在 64 位算术中完成,并且在 128 位比较时它们不需要相等。在实践中这不会发生很多,但它可以发生。
  • 例如,几年前有一个问题是为什么像double x = 123.45 + 7.89; double y = 123.45; double z = 7.89; bool b = x == y + z; 这样的计算有b 等于false,答案是C# 编译器 i> 进行了添加以对x 进行分配,但 runtime 在比较中进行了添加,并且它们以不同的精度级别进行了添加。调查起来很痛苦。
  • 演示该问题的最简单方法是执行复杂的浮点算术运算,但运行程序两次,一次在发布模式下启用优化,一次在调试模式下关闭优化。当优化关闭时,编译器和抖动会关闭许多将浮点计算移入寄存器的优化,因此您很可能会看到调试版本和发布版本之间的差异。这是向编译器团队和 SO 报告的大部分案例。
猜你喜欢
  • 2021-10-10
  • 2013-01-24
  • 2020-05-28
  • 2014-04-25
  • 2020-10-13
  • 1970-01-01
  • 2014-02-07
  • 2021-02-05
  • 1970-01-01
相关资源
最近更新 更多