19

可能重复:
在 C# 中比较双精度值时出现问题

我在其他地方读过,但真的忘记了答案,所以我再次在这里问。这个循环似乎永远不会结束,无论你用任何语言编写它(我用 C#、C++、Java... 测试它):

double d = 2.0;
while(d != 0.0){
   d = d - 0.2;
}
4

9 回答 9

32

浮点计算并不完全精确。您将收到表示错误,因为 0.2 没有作为二进制浮点数的精确表示,因此该值不会完全等于零。尝试添加调试语句以查看问题:

double d = 2.0;
while (d != 0.0)
{
    Console.WriteLine(d);
    d = d - 0.2;
}
2
1,8
1,6
1,4
1,2
1
0,8
0,6
0,4
0,2
2,77555756156289E-16 // 不完全为零!
-0,2
-0,4

解决它的一种方法是使用 type decimal

于 2010-07-28T08:35:25.373 回答
27

(一方面你没有使用相同的变量,但我认为这是一个错字:)

0.2 并不是真正的 0.2。这是最接近double0.2 的值。当你从 2.0 中减去 10 次时,你最终不会得到正好0.0。

在 C# 中,您可以更改为使用decimal类型,这将起作用:

// Works
decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 0.2m;
}

这是有效的,因为十进制类型确实表示像 0.2 这样的十进制值(在限制范围内;它是 128 位类型)。所涉及的每个值都是可精确表示的,因此它有效。什么行不通是这样的:

decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 1m/3m;
}

在这里,“三分之一”不能完全表示,所以我们最终会遇到与以前相同的问题。

但总的来说,在浮点数之间执行精确相等比较是一个坏主意——通常你在一定的公差范围内比较它们。

我有关于C#/.NET 上下文中的浮点二进制点浮点小数点的文章,其中更详细地解释了事情。

于 2010-07-28T08:35:26.633 回答
11

我记得我买了一台 Sinclair ZX-81,读完优秀的 Basic 编程手册,当我遇到第一个浮点舍入错误时,我差点回到商店。

我从来没有想过人们在 27.99998 年后仍然会遇到这些问题。

于 2010-07-28T08:47:40.380 回答
3

你最好使用

while(f  > 0.0) 

*编辑:见下面帕斯卡的评论。但是,如果您确实需要以整数的、确定的次数运行循环,请使用整数数据类型。

于 2010-07-28T08:36:55.390 回答
2

问题是浮点运算。如果一个数字没有精确的二进制表示,那么你只能存储最接近它的数字(就像你不能1/3以十进制存储数字一样 - 你只能存储0.33333333一些长度为 '3' 的东西。)这意味着浮点数的算术通常并不完全准确。尝试以下(Java):

public class Looping {

    public static void main(String[] args) {

        double d = 2.0;
        while(d != 0.0 && d >= 0.0) {
            System.out.println(d);
            d = d - 0.2;
        }

    }

}

你的输出应该是这样的:

2.0
1.8
1.6
1.4000000000000001
1.2000000000000002
1.0000000000000002
0.8000000000000003
0.6000000000000003
0.4000000000000003
0.2000000000000003
2.7755575615628914E-16

现在你应该能够看到为什么这种情况d == 0永远不会发生。(最后一个数字有一个非常接近 0 但不完全的数字。

对于浮点怪异的另一个例子,试试这个:

public class Squaring{

    public static void main(String[] args) {

        double d = 0.1;
        System.out.println(d*d);

    }

}

因为没有精确的二进制表示0.1,所以对其平方不会产生您期望的结果 ( 0.01),但实际上类似于0.010000000000000002!

于 2010-07-28T08:41:47.070 回答
1

f 未初始化;)

如果你的意思是:

double f = 2.0;

这可能是对双变量的非精确算法的影响。

于 2010-07-28T08:35:59.877 回答
1

这是因为浮点的精度。使用 while (d>0.0),或者如果必须,

while (Math.abs(d-0.0) > some_small_value){

}
于 2010-07-28T08:39:29.240 回答
0

它不会停止,因为 0.2 我没有用二进制补码精确表示,所以你的循环永远不会执行0.0==0.0测试

于 2010-07-28T09:24:30.483 回答
0

正如其他人所说,这只是对任何基数进行浮点运算时遇到的一个基本问题。恰好base-2是计算机中最常见的一种(因为它承认有效的硬件实现)。

如果可能的话,最好的解决方法是切换到使用某种数字的商表示进行循环,从而从中派生浮点值。好吧,这听起来有点夸张!对于您的具体情况,我将其写为:

int dTimes10 = 20;
double d;
while(dTimes10 != 0) {
   dTimes10 -= 2;
   d = dTimes10 / 10.0;
}

在这里,我们真的在处理分数 [20/10, 18/10, 16/10, ..., 2/10, 0/10],其中迭代是用整数完成的(即,容易得到正确)在转换为浮点数之前,具有固定分母的分子。如果您可以重写您的实际迭代以像这样工作,您将获得巨大的成功(而且它们实际上并不比您以前所做的贵多少,这是获得正确性的一个很好的权衡)。

如果你不能这样做,你需要使用 equal-within-epsilon 作为比较。大约,这将替换d != targetabs(d - target) < ε,其中 ε (epsilon) 选择有时会很尴尬。基本上,ε 的正确值取决于一系列因素,但对于示例迭代,考虑到步长值的比例,它可能最好选择为 0.001(即,它是步长幅度的 0.5%,所以在此范围内的任何值将是错误而不是信息)。

于 2010-07-28T09:51:50.943 回答