【问题标题】:C# int stored in double "==" precision problemC# int 存储在 double "==" 精度问题
【发布时间】:2011-05-16 12:00:43
【问题描述】:

这里是简化的代码:

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

是否保证a == btrue

【问题讨论】:

  • 为什么不呢?如果有人说不,我当然想知道为什么!
  • 因为例如由于精度问题,一般情况下为 4.0/2.0 != 8.0/4.0。我的问题没有任何计算,所以问题是它是否有助于避免这个问题。
  • 虽然技术上正确,但这是一个糟糕的示例选择,因为这两个值都可以用 IEEE 754 双浮点类型精确表示。
  • @codekaizen,是的,你是对的,我知道。这是一个极其简化的示例。
  • (1.0 / 3.0) * 5.0 != 5.0 / 3.0 可能是更好的选择。

标签: c# double int precision


【解决方案1】:

是的。 32 位整数可以完全表示为 64 位浮点数。

【讨论】:

  • 请参考规范?
  • 我怀疑 C# 规范中有关于这种特殊情况的任何内容,但是如果您查看 IEEE 754 Binary64 格式的规范,您会发现它使用 52 位来编码有效数字,这意味着它可以精确地表示高达2^52 - 1 的整数......或者可能更多。我不完全确定。
  • 隐式数值转换表(C# 参考)~msdn.microsoft.com/en-us/library/y5b434w4.aspx
  • 在任何情况下,即使在计算中出现错误并且i 是某个“关闭”值,您已经为它们设置了ab,因此它们具有相同的(可能不正确)值。这就像问double a = 1; double b = a;,然后问a == b?双精度错误可能很糟糕,但不是这样。即使您从long 转换为double,并且您失去了精度,ab 也会有相同的“不正确”值——在规范中找到这个可能是一个更有趣的问题。
  • 哇,这很复杂。我不认为我原始链接中的引用真的有帮助(对不起@levanovd)。在实际的 C# 语言规范中,它更进一步:“其他隐式数字转换永远不会丢失任何信息”。所以 int to double 没有任何损失。 @Pete,我不知道它与 C++ 相比如何。但是按照原来的例子,value 是一个 32 位的 int,所以它使用的位永远不会超过 52 位。
【解决方案2】:

是否保证 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 位尾数。当指数相差很大时,还会对浮点运算进行舍入。在这种情况下,即使是简单的加法运算也会降低精度,因为 matissas 会移位。

回到以双精度编码的整数——即使除法运算也是精确的,只要结果是整数值。所以4.0/2.0 == 8.0/4.0也保证为真。

当您的数字不是整数时,问题就开始了。但即使在这种情况下,如果数字是 x/2^yx 的形式,也可以保证精确表示,适合 52 位(例如,3/4 5/8 345/1024)。考虑到y 可以对两个操作数相等,对这些数字的操作也是精确的,所以即使:

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

保证为真。

有趣的事实是您可以安全地对 54 位有符号整数执行操作。这是因为您在开头有附加位,其含义由指数编码,还有一个附加位用于符号。现在 -2^53 在 54 位有符号整数不适合尾数的情况下为 MIN_INT,但指数将在尾数全为零的情况下完成这项工作。

【讨论】:

    【解决方案3】:

    是的,您可以在double(64 位浮点数)中存储一个(32 位)整数,而不会损失精度。

    但是,一旦您使用 double 执行计算,您很可能会引入舍入误差,即精度损失。这些错误可能足够小,以至于当您将 double 值转换回 int 时它们会被四舍五入 - 但错误就在那里,所以要注意它。

    它是如何完成的:有关如何将整数存储为浮点值的详细信息,请参阅this document (IEEE Standard 754 Floating Point Numbers by Steve Hollasch)

    总而言之(有点不准确),浮点值由三部分组成:符号位、“小数”部分(称为尾数)和“指数”部分。大致如下:

         = -1符号位 × 分数 × 2指数 em>

    您可以将整数值存储在double 的“分数”部分(52 位宽,对于 32 位整数来说已经足够宽了。“指数”部分可以设置为0,因为不需要。

    【讨论】:

      【解决方案4】:

      我打开了 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 的测试,但我怀疑你会使用这么大的数字。

      【讨论】:

      • 我投了反对票,因为你的结论对我来说似乎很危险(即使你可能是对的):仅仅因为你测试了 一个 简单的场景并不意味着这将总是工作。 (您已经测试了 2^32 中的 1 个案例。您缺乏归纳证明来证明您的结论也适用于所有其他整数。)——一旦您开始进行浮点计算,您就是可能会引入舍入误差。幸运的是,对于整数来说,它们是如此之小,以至于在大多数情况下它们可能微不足道。
      • 反例: 首先,将添加的内容替换为i /= 2; t /= 2;。其次,在相等性测试中明确地进行强制转换:i == (int)t 可能总是会导致 true,因为微小的浮点舍入误差被舍入了。 OTOH,(double)i == t 将导致 false 上面除以 2。-- 结论: 虽然您可能可以在 double存储一个整数而不会丢失精度方面,当您开始进行计算时,情况并非如此,因为您会引入舍入误差!
      • 你读过我的反例吗?它为您提供了一个测试不起作用的情况。您的测试似乎只起作用,因为您依赖于从doubleint 的隐式转换来获取变量t(和k)。但是:it 通常会相等!您只会“看到”平等,因为您正在舍入微小的差异!尝试将这些值与doubles 进行比较,看看会发生什么。
      • 抱歉,在发布我的编辑之前我没有看到那个。你说的对。但是提问者的问题是,如果双打从同一个 int 中获取它们的值,它们是否会相等,而且它们会。如果在设置它们的值后对 int 和 double 进行操作,它们将不再相等,因为 double 可以存储更复杂的数字。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-09-09
      • 1970-01-01
      • 2014-01-25
      • 2017-09-25
      • 1970-01-01
      • 2019-10-23
      • 1970-01-01
      相关资源
      最近更新 更多