【发布时间】:2012-09-28 17:22:38
【问题描述】:
Single符合 IEC 60559:1989 (IEEE 754) 二进制标准 浮点运算。
和 C# 语言规范:
float和double类型使用 32 位单精度表示 和 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));
}
}
除了编写一些环境信息和编译配置外,程序只考虑两个floats(即a和b)及其产品。最后四行是有趣的。这是使用 Debug x86(左)、Release x86(中)和 x64 编译后在 64 位机器上运行的输出(右):
我们得出结论,简单的float 操作的结果取决于构建配置。
"case .58" 之后的第一行是对两个floats 相等性的简单检查。我们希望它独立于构建模式,但事实并非如此。接下来的两行我们希望是相同的,因为它不会改变将float 转换为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(四舍五入到最近),所以我们向上取整。所以我们得出结论,根据 IEEE,结果是58f。根据 IEEE 的说法,这在任何情况下都没有先验,例如0.59f * 100f 小于59f,而0.60f * 100f 大于60f。
所以看起来 x64 版本的代码是正确的(上图中最右边的输出窗口)。
注意:如果这个问题的任何读者有一个旧的 32 位 CPU,听听上面程序的输出在他们的架构上是什么会很有趣。
现在是问题:
- 以上是bug吗?
- 如果这不是错误,C# Specifcation 中的哪个位置说运行时可以选择以额外精度执行
float乘法,然后“忘记”再次摆脱该精度? - 如何将
float表达式转换为float类型有什么改变? - 看似无害的操作,例如将一个表达式拆分为两个表达式,这不是一个问题吗?将
(a*b)提取到临时局部变量会改变行为,当它们应该在数学上(根据 IEEE)等效时?程序员如何提前知道运行时是否选择以“人工”额外(64 位)精度保存float? - 为什么允许在发布模式下编译的“优化”来更改算法?
(这是在 .NET Framework 4.0 版本中完成的。)
【问题讨论】:
-
这就是为什么我们永远不应该检查浮点相等性;p
-
@leppie 好吧,检查“小于”或“大于”(即使在添加或减去一些
epsilon之后)也会以完全类似的方式产生不可预测的结果。因此,当您的程序在某处使用浮点变量时,您永远无法知道程序是否始终如一地执行相同操作,因为 CLR 中的浮点运算是不可预测的? -
我有点讽刺。 :)
标签: c# floating-point 64-bit clr single-precision