在 C# 中复制后是否保证双精度/浮点数相等?[复制]

Is double/float equality guaranteed after copying in C#? [duplicate]

提问人:Gregory Fenn 提问时间:10/24/2018 更新时间:10/24/2018 访问量:382

问:

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

在 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

并且可能取决于我问的问题是版本 1 还是版本 2?(我们可以假设 并且接近 double 的最大值或最小值,也不接近正负 epsilon)。bxy

谢谢! ~格雷格

C# 浮点 相等

评论

0赞 hatchet - done with SOverflow 10/24/2018
另请参阅浮点算术是否稳定,这是一个与您的问题更接近的重复问题。
0赞 Eric Lippert 10/24/2018
简而言之:任何双重计算都可以在任何时候以 64 位或更高的精度级别进行,无论出于种原因,这都会影响相等性。C# 只需要在分配给字段或数组元素时“舍入”到 64 位;本地人和临时工可以随心所欲地进出 80 位或 128 位精度。是的,这太可怕了,你应该把责任归咎于制造芯片的芯片设计师,因为做确定性数学的成本更高。

答:

-1赞 Olivier Jacot-Descombes 10/24/2018 #1

复制不会造成任何精度损失。y = x;

赋值后,为 true。x == y

由于 和 相等,之后xy

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

x == y很可能是真的;然而,正如埃里克·利珀特(Eric Lippert)所指出的那样(见下面的评论),这并不能保证。

始终将双打与所需的精度进行比较:

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

当然,如果您只注释掉其中之一,并且会相差大约.它们可能会有所不同。如果两者都非常大(例如),则加法将不起作用,因为它会影响被截断的小数点。 精度为 15-16 位。如果两者都非常小(例如),也是如此,因为这样它们的原始值将丢失,结果将是 。xy1.41.3999999999999991.4000000000000011.0e25double1.0e-251.4

在 C# 交互窗口中,您可以测试它

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

并且

> 1e-25 + 1.4 == 1.4
true

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

嗯,不完全是。 (C# 别名 )是一种值类型。 是字面意思。标识符中是一个变量(如果是在类型级别声明的,则为字段)。System.Doubledouble3.141592654doubledouble 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.

...变量的值是按值复制的。不同之处在于,此值是参考值。该值不是 person 对象,而是对它的引用。

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.
}

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

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

评论

1赞 TommyD 10/24/2018
请注意,如果 x 或 y 是 NAN,则即使使用 x==x,== 运算符的计算结果也会为 false。
1赞 Eric Lippert 10/24/2018
可悲的是,这个答案是错误的。C# 不需要以相同的精度级别进行任何两个计算,这可能会影响相等性。
1赞 Eric Lippert 10/24/2018
此问题已作为重复问题关闭;有关详细信息,请参阅重复问题。简短的版本是:浮点寄存器允许为 80 或 128 位或任何位,但字段和堆栈插槽保证仅为 64 位,并且允许 C# 将浮点移入和移出寄存器,但抖动感觉性能最高。这意味着一个计算可以用 128 位算术完成,另一个计算可以用 64 位算术完成,并且在 128 位比较时它们不必相等。在实践中,这种情况并不经常发生,但可能会发生。
1赞 Eric Lippert 10/24/2018
For example, there was a question a few years back about why some computation like had equal to , and the answer was that the C# compiler did the addition to do the assignment to but the runtime did the addition in the comparison, and they did them at different levels of precision. That one was a pain to investigate.double x = 123.45 + 7.89; double y = 123.45; double z = 7.89; bool b = x == y + z;bfalsex
1赞 Eric Lippert 10/24/2018
The easiest way to demonstrate the issue is to do a complicated floating point arithmetic operation, but run the program twice, once in release mode with optimizations on, and once in debug mode with optimizations off. The compiler and jitter turn off many of the optimizations that move floating point computations into registers when optimizations are off, and so you're most likely to see a difference between the debug and release versions. That's the considerable bulk of the cases that were reported to the compiler team, and on SO.