为什么 C# 中的浮点运算不精确?

Why is floating point arithmetic in C# imprecise?

提问人:Prankster 提问时间:4/16/2009 最后编辑:Mark LakataPrankster 更新时间:1/28/2022 访问量:29810

问:

为什么以下程序会打印它打印的内容?

class Program
{
    static void Main(string[] args)
    {
        float f1 = 0.09f*100f;
        float f2 = 0.09f*99.999999f;

        Console.WriteLine(f1 > f2);
    }
}

输出为

false
C# 浮点

评论

0赞 Daniel A. White 4/16/2009
你为什么不自己尝试一下呢?
22赞 Prankster 4/16/2009
我试过了,我只是想知道为什么我看到我所看到的。
2赞 phuclv 5/15/2015
与语言无关 - 浮点数学是否被破坏了?
1赞 Liam 11/23/2017
浮点数学是否损坏的可能重复?
0赞 Jonathan Hall 2/15/2019
为什么浮点数不准确的可能重复?

答:

43赞 Michael 4/16/2009 #1

浮点只有这么多位数的精度。如果您看到 f1 == f2,那是因为任何差异都需要比 32 位浮点数更精确的精度。

我推荐阅读《每个计算机科学家都应该读的关于浮点的知识

评论

0赞 dirkgently 4/16/2009
+1 为什么,为什么我在点击提交按钮后看到有人发布了链接?:P
1赞 James 4/16/2009
+1 msdn.microsoft.com/en-us/library/b1e65aza(VS.71).aspx Michael 是正确的,C# float 只有 7 位精度,而 99.999999f 有 8 位。
6赞 Wedge 4/16/2009
这不是特定于 C# 的。单精度 IEEE-754 浮点数仅为 32 位,这给出了大约 7 位十进制数字的精度。如果你想要比这更好的,请使用双倍。
0赞 RBerteig 4/17/2009
双打只是把问题推了几个位。您真正想要的是像 QD 库中的四双倍(参见 crd.lbl.gov/~dhbailey/mpdist)。那里也有一个任意精度的库。
9赞 Joel Coehoorn 4/16/2009 #2

最主要的是,这不仅仅是 .Net:它是大多数每种语言用来表示内存中浮点数的底层系统的限制。精度仅到此为止。

你也可以用相对简单的数字来获得一些乐趣,当你考虑到它甚至不是十进制时。例如,0.1(1/10th)在以二进制表示时是重复的小数,就像 1/3rd 在以十进制表示时一样。

7赞 Jim 4/5/2015 #3

在这种特殊情况下,这是因为 .09 和 .999999 不能以二进制的精确精度表示(同样,1/3 不能以十进制的精确精度表示)。例如,0.1111111111111111111111011111 以 2 为基数 0.999998986721038818359375 以 10 为基数。将 1 添加到前一个二进制值,0.111111111111111111111111 以 2 为基数 2 是以 0.999999904632568359375 为基数 10。没有正好 0.999999 的二进制值。浮点精度还受到为存储尾数的指数和小数部分而分配的空间的限制。此外,与整数类型一样,浮点型可以溢出其范围,尽管其范围大于整数范围。

在 Xcode 调试器中运行这一段 C++ 代码,

浮点数 myFloat = 0.1;

显示 myFloat 获取值 0.100000001。它偏离了 0.000000001。不是很多,但如果计算有多个算术运算,那么不精确性可能会加剧。

恕我直言,对浮点的一个很好的解释是在加州州立大学索诺玛分校的 Bob Plantz(已退休)http://bob.cs.sonoma.edu/getting_book.html 的 Introduction to Computer Organization with x86-64 Assembly Language & GNU/Linux 的第 14 章中。以下内容以该章为基础。

浮点数类似于科学记数法,其中值存储为大于或等于 1.0 且小于 2.0 的混合数(尾数),乘以另一个数字的某种幂(指数)。浮点使用以 2 为基数而不是以 10 为基数,但在 Plantz 给出的简单模型中,为了清楚起见,他使用以 10 为基数。想象一个系统,其中两个存储位置用于尾数,一个位置用于指数*的符号(0 代表 +,1 代表 -),一个位置用于指数。现在添加 0.93 和 0.91。答案是1.8,而不是1.84。

9311 表示 0.93,即 9.3 乘以 10 到 -1。

9111 表示 0.91,即 9.1 乘以 10 到 -1。

确切的答案是 1.84,即 1.84 乘以 10 到 0,如果我们有 5 个位置,这将是 18400,但是,只有四个位置,答案是 1800,或 1.8 乘以 10 到 0,或 1.8。当然,浮点数据类型可以使用四个以上的存储位置,但位置的数量仍然有限。

精度不仅受到空间的限制,而且“二进制小数值的精确表示仅限于 2 的反幂之和。“(普朗茨,同前)。

0.11100110(二进制)= 0.89843750(十进制)

0.11100111(二进制)= 0.90234375(十进制)

二进制中没有 0.9 小数的精确表示。即使将分数带到更多的地方也行不通,因为您会在右边永远重复 1100。

初级程序员通常认为浮点运算更多 比整数准确。的确,即使加两个非常大 整数可能会导致溢出。乘法使它更有可能 结果将非常大,因此会溢出。使用时 对于两个整数,C/C++ 中的 / 运算符会导致小数部分 迷失。然而。。。浮点表示有自己的 一组不准确之处。“(普朗茨,同前。

*在浮点数中,表示数字的符号和指数的符号。