在64位计算机上运行x86编译的代码时,单精度算术被破坏

时间:2012-09-28 17:22:39

标签: c# floating-point 64-bit clr single-precision

当您阅读MSDN on System.Single时:

  

Single符合IEC 60559:1989(IEEE 754)二进制标准   浮点运算。

和C#语言规范:

  

使用32位单精度表示floatdouble类型   和64位双精度IEEE 754格式[...]

以后:

  

根据IEEE 754算法的规则计算产品。

您很容易得到float类型及其乘法符合IEEE 754的印象。

IEEE 754的一部分 multiplcation明确定义。我的意思是,当你有两个float个实例时,只存在一个float using System; static class Program { static void Main() { Console.WriteLine("Environment"); Console.WriteLine(Environment.Is64BitOperatingSystem); Console.WriteLine(Environment.Is64BitProcess); bool isDebug = false; #if DEBUG isDebug = true; #endif Console.WriteLine(isDebug); Console.WriteLine(); float a, b, product, whole; Console.WriteLine("case .58"); a = 0.58f; b = 100f; product = a * b; whole = 58f; Console.WriteLine(whole == product); Console.WriteLine((a * b) == product); Console.WriteLine((float)(a * b) == product); Console.WriteLine((int)(a * b)); } } 这是他们的“正确”产品。不允许产品依赖于计算系统的某些“状态”或“设置”。

现在,请考虑以下简单程序:

float

通过写一些关于环境的信息并编译配置,该程序只考虑两个a(即bfloat)及其产品。最后四个写行是有趣的。这是在使用调试x86 (左),发布x86 (中)和 x64 进行编译后在64位计算机上运行此输出的输出(右):

Debug x86 (left), Release x86 (middle), and x64 (right)

我们得出结论,简单"case .58"操作的结果取决于构建配置。

float之后的第一行是对两个float的相等性的简单检查。我们希望它独立于构建模式,但事实并非如此。接下来的两行我们希望是相同的,因为它不会改变任何内容以将float转换为"True↩ True"。但他们不是。我们还希望他们阅读a*b,因为我们正在将产品0.58与自身进行比较。我们希望输出的最后一行与构建配置无关,但事实并非如此。

要弄清楚正确的产品是什么,我们手动计算。 a0 . 1(001 0100 0111 1010 1110 0)(001 0100 0111 1010 1110 0)... )的二进制表示形式为:

0 . 1(001 0100 0111 1010 1110 0)(001      (*)

括号中的块是永远重复的句号。此数字的单精度表示需要四舍五入为:

Single

我们将舍入(在本例中为向下舍入)舍入为最接近的可表示b。现在,数字“一百”(110 0100 . (**) )是:

(*)

二进制。计算数字(**) 11 1001 . 1111 1111 1111 1111 1110 0100 的完整产品会给出:

 11 1010 . 0000 0000 0000 0000 00

舍入(在本例中为四舍五入)为单精度给出

1

我们向上舍入,因为下一位是0,而不是58f(舍入到最近)。因此我们得出结论,根据IEEE,结果是0.59f * 100f。这不是以任何方式给出的先验,例如59f小于0.60f * 100f,而60f大于float,根据IEEE

所以看起来代码的x64版本是正确的(上图中最右边的输出窗口)。

注意:如果这个问题的任何读者都有一个旧的32位CPU,那么听听上述程序的输出在他们的架构上会很有趣。

现在问题是:

  1. 以上是错误吗?
  2. 如果这不是一个错误,那么在C# Specifcation中它表示运行时可能选择以额外的精度执行float乘法,然后“忘记”再次摆脱该精度?
  3. 如何将float表达式转换为(a*b)类型可以改变任何内容?
  4. 看似无辜的操作,例如通过例如将表达式分成两个表达式,这不是一个问题吗?将float拉出到一个临时局部变量,当它们应该在数学上(根据IEEE)等效时,改变行为?如果运行时选择以“人工”额外(64位)精度保持{{1}},程序员如何才能提前知道?
  5. 为什么在发布模式下编译的“优化”允许更改算术?
  6. (这是在.NET Framework的4.0版本中完成的。)

1 个答案:

答案 0 :(得分:8)

我没有检查过你的算术,但我以前肯定看过类似的结果。除了调试模式有所不同之外,分配局部变量和实例变量也会产生影响。根据C#4规范的第4.1.6节,这是合法的:

  

可以以比操作的结果类型更高的精度执行浮点运算。例如,某些硬件体系结构支持“扩展”或“长双”浮点类型,其范围和精度高于double类型,并使用此更高精度类型隐式执行所有浮点运算。只有在性能成本过高的情况下,才能使这种硬件架构以 less 精度执行浮点运算。 C#允许更高精度的类型用于所有浮点运算,而不是要求实现放弃性能和精度。除了提供更精确的结果外,这几乎没有任何可衡量的影响。 [...]

我不能肯定地说这是不是在这里发生了什么,但我不会感到惊讶。