当您阅读MSDN 时System.Single
:
Single
符合二进制浮点运算的 IEC 60559:1989 (IEEE 754) 标准。
和 C# 语言规范:
和类型使用 32 位单精度
float
和double
64 位双精度 IEEE 754 格式表示 [...]
然后:
该乘积是根据 IEEE 754 算术规则计算的。
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(即a
和b
)及其乘积。最后四行是有趣的。以下是使用Debug x86(左)、Release x86(中)和x64(右)编译后在 64 位机器上运行的输出:
我们得出结论,简单float
操作的结果取决于构建配置。
之后的第一行是对两个s"case .58"
的简单检查。float
我们希望它独立于构建模式,但事实并非如此。我们希望接下来的两行是相同的,因为它不会改变任何将 afloat
转换为 a 的内容float
。但他们不是。我们也希望他们能够阅读"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 的。根据 IEEE,这不是以任何方式先验给出的,例如0.59f * 100f
小于59f
和0.60f * 100f
大于。60f
所以看起来 x64 版本的代码是正确的(上图中最右边的输出窗口)。
注意:如果这个问题的任何读者有一个旧的 32 位 CPU,听听上面程序在他们的架构上的输出会很有趣。
现在的问题:
- 以上是bug吗?
- 如果这不是一个错误,那么C# 规范中的哪个位置说运行时可以选择执行
float
具有额外精度的乘法然后“忘记”以再次摆脱该精度? - 将
float
表达式转换为类型如何float
改变任何东西? (a*b)
当它们在数学上(根据 IEEE)应该是等效的(根据 IEEE)时,看似无辜的操作(例如通过将一个拉出到一个临时局部变量)将一个表达式拆分为两个表达式会改变行为,这难道不是一个问题吗?程序员如何提前知道运行时是否选择保持float
“人工”额外(64 位)精度?- 为什么允许在发布模式下编译的“优化”改变算术?
(这是在 .NET Framework 的 4.0 版本中完成的。)