C#中存储在double中的int型数值"=="运算符精度问题

9

以下是简化后的代码:

int i = 1;
double a = i;
double b = i;

a == btrue”这个说法是否保证正确?

为什么不呢?如果有人说不,我肯定想知道为什么! - Roopesh Shenoy
因为精度问题,在一般情况下,例如 4.0/2.0 != 8.0/4.0。我的问题中没有任何计算,所以问题是是否有助于避免这个问题。 - levanovd
1
虽然在技术上是正确的,但这个示例选择不好,因为这两个值都可以由IEEE 754双精度浮点类型精确表示。 - codekaizen
@codekaizen,是的,你说得对。我知道这一点。这只是一个非常简化的例子。 - levanovd
1
(1.0 / 3.0) * 5.0 != 5.0 / 3.0 可能是更好的选择。 - codekaizen
4个回答

11

可以。32位整数可以精确地表示为64位浮点数。


有关规范的参考资料吗? - levanovd
1
我怀疑C#规范中没有关于这个特定情况的说明,但是如果你查看IEEE 754 Binary64格式的规范,你会发现它使用52位来编码有效数字,这意味着它可以精确地表示高达2^52 - 1的整数...或者可能更多。我不是完全确定。 - Ian Henry
2
无论如何,即使计算中出现错误并且i是某个“偏离”值,您已将ab设置为它们,因此它们具有相同的(潜在不正确的)值。这就像问double a = 1; double b = a;,那么a == b吗?双精度误差可能很讨厌,但不会像这样。即使您从long转换为double,并且失去了精度,ab也都具有相同的“不正确”值--在规范中找到这一点可能是一个更有趣的问题。 - Ian Henry
1
哇,这变得复杂了。我不认为我原始链接中的引用真的有帮助(抱歉@levanovd)。在实际的C#语言规范中,它更进一步:“其他隐式数字转换永远不会丢失任何信息”。因此,int到double不会丢失任何信息。@Pete,我不知道它与C++相比如何。但是根据原始示例,value是一个32位int,因此它永远不会使用超过52位。 - Nigel Whatling
我得到了一个徽章,提醒我五年前的这个答案,并且我想回复我的评论,“精度误差可能很难处理,但不是像这样。”这并不适用于C#,但这是一篇非常棒的阅读材料:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323#c109 - Ian Henry
显示剩余4条评论

9
“a == b”是否保证为真?
是的。这是因为您执行了相同的转换两次,并且由于其确定性行为,无论舍入问题如何,您最终都将获得相同的值。
我们可以将您的问题概括为:
“我们是否可以在double类型中对32位整数值执行算术运算而不失去精度?”
答案也是肯定的。简单的解释是,如果只有可能,那么在尾数(参见http://en.wikipedia.org/wiki/Significand)的操作是精确的,在32位整数值的情况下,这是可能的。
更长的故事在这里。只要您的整数值适合称为尾数的52位小数部分(请参见http://en.wikipedia.org/wiki/Double_precision),则使用double进行的所有整数值计算都将完全正常。
这是因为您的数字(比如173,即二进制0000010101101b)将被表示为1.010110100000b*2^7,这是准确的。
只要适合于尾数,对尾数的所有操作都是直接的。当特定操作的结果不适合尾数时,会对整数进行舍入——例如,您将40位尾数乘以40位尾数时。当指数差异很大时,浮点运算还会出现舍入。在这种情况下,即使是简单的加法操作也可能失去精度,因为尾数被移位了。
回到双精度编码的整数上——即使是除法操作也是精确的,只要结果是整数值。所以4.0/2.0 == 8.0/4.0也保证是正确的。
当您的数字不是整数时,问题就开始了。但是,在这种情况下,如果它们是形式为x/2^y并且x适合52位(例如3/4 5/8 345/1024),则保证准确地表示数字。如果对这些数字进行操作,也是精确的,前提是y可以对两个操作数都相等,所以即使:

123456789/1024/1024/1024/1024 == 
(23456789/1024/1024/1024/1024 +
100000000/1024/1024/1024/1024)

保证为真。

有趣的事实是,您可以安全地对54位有符号整数执行操作。这是因为在开头有一个附加位,其含义由指数编码,另外还有一位用于表示符号。现在,在54位有符号整数的情况下,-2 ^ 53(如果是MIN_INT)无法适应尾数,但指数将在尾数填满零的情况下完成此工作。


3
是的,您可以将(32位)整数存储在double(64位浮点数)中,而不会丢失精度。
但是,一旦您对double执行计算,您很可能会引入舍入误差,即精度损失。这些错误可能足够小,以便在将double值强制转换回int时被四舍五入-但是错误确实存在,请注意。
如何完成:有关将整数存储为浮点值的详细信息,请参见Steve Hollasch的此文档(IEEE标准754浮点数)
简而言之(略有不准确),浮点值由三个部分组成:符号位、“分数”部分(称为尾数)和“指数”部分。它们大致组合如下:
 value = -1 sign bit × fraction × 2 exponent 您可以将整数值存储在double的“分数”部分中(其宽度为52位,足以容纳32位整数)。 “指数”部分可以设置为0,因为不需要。

-2
我打开了 Visual Studio,然后进行了测试。
以下是我的代码:
int i = 5;
double t = i;
double k = i;
MessageBox.Show((i == t).ToString()); //true
MessageBox.Show((k == t).ToString()); //true
i += 5;
t += 5;
k = i;
MessageBox.Show((i == t).ToString()); //true
MessageBox.Show((k == t).ToString()); //true
i += (int)Math.Round(5.6);
t += 5.6;
t = (int)Math.Round(t);
k = i;
MessageBox.Show((i == t).ToString()); //true
MessageBox.Show((k == t).ToString()); //true
i = int.MaxValue - 5438;
t = int.MaxValue - 5438;
k = i;
MessageBox.Show((i == t).ToString()); //true
MessageBox.Show((k == t).ToString()); //true
i = (int)Math.Round(double.MaxValue);
t = Math.Round(double.MaxValue);
k = i;
MessageBox.Show((i == t).ToString()); //false
MessageBox.Show((k == t).ToString()); //false
i = (int)Math.Round(double.MaxValue);
t = i;
k = i;
MessageBox.Show((i == t).ToString()); //true
MessageBox.Show((k == t).ToString()); //true

结果是两个消息框都显示“真”字。

我猜结论就是:是的,你可以保证它是真的。

编辑:我对我的测试进行了扩展。唯一返回 false 的测试是在 double.MaxValue 的测试,但我怀疑您不会使用那么大的数字。


3
我下了反对票,因为你的结论对我来说似乎相当危险(尽管你可能是正确的):仅仅因为你测试了一个简单场景,并不意味着这将总是有效。(你只测试了2^32中的1个案例。你缺少归纳证明来证明你的结论对所有其他整数也是有效的。)--一旦你开始进行浮点数计算,你可能会引入舍入误差。幸运的是,对于整数来说,这些误差非常小,很可能在大多数情况下是微不足道的。 - stakx - no longer contributing
2
反例:首先,将加法替换为 i /= 2; t /= 2;。其次,在等式测试中显式地进行强制转换:i == (int)t 很可能总是会得到 true,因为微小的浮点舍入误差被舍去了。另一方面,由于上述除以 2,(double)i == t 将得到 false。-- 结论:虽然您可以在 double 中存储整数而不会失去精度,但一旦开始进行计算,就会引入舍入误差! - stakx - no longer contributing
你读了我的反例吗?它给出了一个情况,你的测试不起作用。你的测试似乎只是因为你依赖于从doubleint的隐式转换,对于变量t(和k)。但是:it通常相等!你只能“看到”相等,因为你舍入了小差异!尝试将这些值作为double进行比较,看看会发生什么。 - stakx - no longer contributing
抱歉,在我发布我的编辑之前,我没有看到那个。你是对的。但是提问者的问题是,如果双精度浮点数从相同的整数获取它们的值,它们是否相等,答案是肯定的。如果在设置它们的值后对整数和双精度浮点数进行操作,它们将不再相等,因为双精度浮点数可以存储更复杂的数字。 - Jan Sverre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接