c# - 在 64 位机器上运行 x86 编译代码时,单精度算术被破坏

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

当您阅读 MSDN on System.Single :

Single complies with the IEC 60559:1989 (IEEE 754) standard for binary floating-point arithmetic.



和 C# 语言规范:

The float and double types are represented using the 32-bit single-precision and 64-bit double-precision IEEE 754 formats [...]



然后:

The product is computed according to the rules of IEEE 754 arithmetic.



你很容易给人的印象是float类型及其乘法符合 IEEE 754。

是 IEEE 754 的一部分乘法是明确定义的 .我的意思是当你有两个 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 s(即 ab )及其产品。最后四个写行是有趣的。这是在使用 编译后在 64 位机器上运行的输出调试 x86 (左), 发布 x86 (中)和 x64 (对):

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

我们得出结论,简单 float 的结果操作取决于构建配置。
"case .58"之后的第一行是对两个 float 相等的简单检查s。我们希望它独立于构建模式,但事实并非如此。我们希望接下来的两行是相同的,因为它不会改变任何内容来转换 floatfloat .但他们不是。我们也希望他们阅读 "True↩ True"因为我们正在比较产品 a*b给自己。我们期望输出的最后一行独立于构建配置,但事实并非如此。

为了找出正确的产品,我们手动计算。 0.58的二进制表示( a ) 是:
0 . 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 ,根据 IEEE。

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

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

现在的问题是:
  • 以上是bug吗?
  • 如果这不是错误,则在 C# Specifcation 中的位置是不是说运行时可能会选择执行 float以额外的精度进行乘法,然后“忘记”再次摆脱该精度?
  • 怎样才能类型转换一个float表达式到类型 float改变什么?
  • 看似无害的操作,例如将一个表达式拆分为两个表达式,难道不是一个问题吗?拉出 (a*b)到临时局部变量,改变行为,当它们应该在数学上(根据 IEEE)等效时?程序员如何提前知道运行时是否选择持有float是否具有“人工”额外(64 位)精度?
  • 为什么允许在 Release模式下编译的“优化”改变算法?

  • (这是在 .NET Framework 4.0 版本中完成的。)

    最佳答案

    我没有检查过你的算术,但我以前肯定见过类似的结果。除了 Debug模式有所作为之外,分配给局部变量与实例变量也可以有所不同。根据 C# 4 规范的第 4.1.6 节,这是合法的:

    Floating point operations may be performed with higher precision than the result type of the operation. For example, some hardware architectures support an "extended" or "long double" floating point type with greater range and precision than the double type, and implicitly perform all floating point operations using this higher precision type. Only at excessive cost in performance can such hardware architectures be made to perform floating point operations with less precision. Rather than require an implementation to forfeit performance and precision, C# allows a higher precision type to be used for all floating point operations. Other than delivering more precise results, this rarely has any measurable effects. [...]



    我不能肯定地说这是否是这里发生的事情,但我不会感到惊讶。

    关于c# - 在 64 位机器上运行 x86 编译代码时,单精度算术被破坏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12644591/

    相关文章:

    c# - 通过安全套接字 (https) 的 C# XML RPC 客户端

    c# - 如何从 CompletedEventArgs 获取抛出异常的类型?

    c++ - 设置精度和裁剪尾随零但从不打印指数

    floating-point - float 的 PI 和精度

    floating-point - 浮点: is a/b - 1 > 0 if a > b

    linux - 64 位 linux,汇编语言,问题?

    javascript - 将数据从 Jquery 发送到 Controller 。 MVC C#

    c# - 使用 C# 使用 iTextsharp 突出显示现有 PDF 的文本(颜色)

    debugging - DebugBreak()导致Win7 x64卡在一台特定计算机上?可能的设置问题?

    c - IMAGE_REL_AMD64_ADDR64 64 位重定位