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

Floating point 为什么这个循环永远不会结束?,floating-point,ieee-754,Floating Point,Ieee 754,可能重复: 我在别处读过,但真的忘记了答案,所以我又问了一遍。这个循环似乎永远不会结束,不管你用什么语言来编码它(我在C语言,C++,java……中测试):< /P> 浮点计算不是非常精确的。您将得到一个表示错误,因为0.2没有作为二进制浮点数的精确表示,因此该值不完全等于零。尝试添加调试语句以查看问题: double d = 2.0; while (d != 0.0) { Console.WriteLine(d); d = d - 0.2; } 2. 1,8 1,6 1,4

可能重复:

我在别处读过,但真的忘记了答案,所以我又问了一遍。这个循环似乎永远不会结束,不管你用什么语言来编码它(我在C语言,C++,java……中测试):< /P>
浮点计算不是非常精确的。您将得到一个表示错误,因为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 277555756156289E-16//不完全是零!! -0,2 -0,4 解决这个问题的一种方法是使用类型
decimal

(有一点,您在整个过程中没有使用相同的变量,但我假设这是一个输入错误:)

0.2并不是真正的0.2。它是最接近0.2的
双精度
值。当你从2.0中减去10倍,你不会得到0.0

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

// Works
decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 0.2m;
}
这是因为decimal类型确实精确地表示类似于0.2的十进制值(在限制范围内;它是128位类型)。所涉及的每一个值都是可精确表示的,因此它是有效的。不起作用的是:

decimal d = 2.0m;
while (d != 0.0m) {
   d = d - 1m/3m;
}
在这里,“第三个”并不是完全可以表示的,所以我们最终遇到的问题和以前一样

不过,一般来说,在浮点数之间执行精确的相等比较是一个坏主意——通常在一定的公差范围内进行比较

我有一些关于C#/.NET上下文的文章,这些文章更详细地解释了事情。

f是未初始化的;)

如果你的意思是:

double f = 2.0;

这可能是对双变量的非精确人工影响。

最好使用

while(f  > 0.0) 

*编辑:参见下面的pascal评论。但是,如果确实需要以整数、确定性的次数运行循环,则应使用整数数据类型。

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

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

}

问题是浮点运算。如果一个数字没有精确的二进制表示形式,那么您只能存储与之最接近的数字(就像您不能将数字
1/3
存储为十进制一样-您只能将类似
0.33333333
的内容存储一段“3”的长度。)这意味着浮点数的算术运算通常不是完全精确的。尝试以下方法(Java):

您的输出应该类似于:

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

我记得我买了一台Sinclair ZX-81,正在阅读优秀的基本编程手册,当我第一次遇到浮点舍入错误时,我几乎要回到商店


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

它不会停止,因为0.2我没有精确地表示在2的补码中
因此,您的循环从不执行
0.0==0.0
test

,正如其他人所说,这只是对任何基进行浮点运算时遇到的一个基本问题。碰巧的是,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 in epsilon作为你的比较。大约,这将
d!=target
替换为
abs(d-target)<ε
,其中ε(epsilon)选择有时会很尴尬。基本上,ε的正确值取决于一系列因素,但在给定步长值的尺度的示例迭代中,它可能最好选择为0.001(即,它是步长大小的百分之五十,因此在该范围内的任何内容都将是错误的,而不是信息性的).

永远不要使用带有浮点值的
=
。可能使用类似于
f>epsilon
的东西。在Java中,我认为有“Strictfp”此类修改器situations@Phobia:
strictfp
不会有帮助。这是所有浮点数固有的问题,无论大小或精度如何。唯一的解决方法是使用除2以外的基数作为指数,我不知道有任何实现可以做到这一点。@cHao:IBM Power机器支持IEEE 754:2008定义十进制浮点类型的修订标准。使用“double”即使在那里,代码也不起作用;使用“decimal64”(decimal32,decimal128)代替它——加上一些拼写或其他——将准确地工作。(另请参阅。)我知道我应该使用它,但为什么f!=0.0不起作用?如果最后一个
f
是2.0e-16,您可能会运行太多次…“永远不会完全准确”在我看来,这是言过其实了。仅仅因为不是每个十进制值都可以用二进制浮点数精确表示,并不能使精确表示的0.25和0.25相加得到精确的0.5,比如说。
2.0
1.8
1.6
1.4000000000000001
1.2000000000000002
1.0000000000000002
0.8000000000000003
0.6000000000000003
0.4000000000000003
0.2000000000000003
2.7755575615628914E-16
public class Squaring{

    public static void main(String[] args) {

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

    }

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