为什么这个循环永远不会结束?

时间:2010-07-28 08:34:17

标签: floating-point ieee-754

  

可能重复:
  problem in comparing double values in C#

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

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

9 个答案:

答案 0 :(得分: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   // Not exactly zero!!
-0,2
-0,4

解决问题的一种方法是使用decimal类型。

答案 1 :(得分:27)

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

0.2不是0.2。它是最接近double的值0.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上下文中有关于floating binary pointfloating decimal point的文章,它们更详细地解释了这些内容。

答案 2 :(得分:11)

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

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

答案 3 :(得分:3)

你最好使用

while(f  > 0.0) 

*编辑:请参阅下面的pascal评论。但是如果你确实需要运行循环一个完整的,确定的次数,而是使用整数数据类型。

答案 4 :(得分: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

答案 5 :(得分:1)

f未初始化;)

如果你的意思是:

double f = 2.0;

这可能是非精确关节炎对双重变量的影响。

答案 6 :(得分:1)

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

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

}

答案 7 :(得分:0)

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

答案 8 :(得分: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 / 0],其中迭代是用整数完成的(即,很容易获得在转换为浮点数之前,在具有固定分母的分子中。如果你可以重写你的真实的迭代来做这样的工作,那么你将获得巨大的成功(并且它们实际上并不比你之前所做的那么昂贵,这是一个很好的权衡为了得到正确性。)

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