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

发布于 2024-09-12 06:14:25 字数 344 浏览 2 评论 0原文

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

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

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

Possible Duplicate:
problem in comparing double values in C#

I've read it elsewhere, but really forget the answer so I ask here again. This loop seems never end regardless you code it in any language (I test it in C#, C++, Java...):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

久随 2024-09-19 06:14:25

浮点计算并不完全精确。您将收到表示错误,因为 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类型。

Floating point calculations are not perfectly precise. You will get a representation error because 0.2 doesn't have an exact representation as a binary floating point number so the value doesn't become exactly equal to zero. Try adding a debug statement to see the problem:

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

One way to solve it is to use the type decimal.

何以笙箫默 2024-09-19 06:14:25

(一方面,您始终没有使用相同的变量,但我认为这是一个拼写错误:)

0.2 并不是真正的 0.2。它是最接近 0.2 的 double 值。当你用 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 上下文的浮动小数点,它更详细地解释了事情。

(For one thing you're not using the same variable throughout, but I'll assume that's a typo :)

0.2 isn't really 0.2. It's the closest double value to 0.2. When you've subtracted that 10 times from 2.0, you won't end up with exactly 0.0.

In C# you can change to use the decimal type instead, which will work:

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

This works because the decimal type does represent decimal values like 0.2 precisely (within limits; it's a 128-bit type). Every value involved is precisely representable, so it works. What wouldn't work would be this:

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

Here, "a third" isn't exactly representable so we end up with the same problem as before.

In general though, it's a bad idea to perform exact equality comparisons between floating point numbers - usually you compare them within a certain tolerance.

I have articles on floating binary point and floating decimal point from a C#/.NET context, which explain things in more detail.

眼睛会笑 2024-09-19 06:14:25

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

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

I remember buying a Sinclair ZX-81, working my way through the excellent Basic programming manual, and nearly returning to the shop when I came across my first floating point rounding error.

I'd never have have imagined that people would still be having these problems 27.99998 years later.

递刀给你 2024-09-19 06:14:25

你最好使用

while(f  > 0.0) 

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

You are better off using

while(f  > 0.0) 

*edit : See pascal's comment below. But if you do need to run a loop an integral, deterministic number of times, rather use an integral data type.

醉梦枕江山 2024-09-19 06:14:25

问题是浮点运算。如果数字没有精确的二进制表示形式,那么您只能存储最接近的数字(就像您无法存储十进制数字 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

The problem is floating point arithmetic. If there is no exact binary representation for a number, then you can only store the closest number to it (just like you couldn't store the number 1/3 in decimal - you can only store something like 0.33333333 for some length of '3's.) This means that arithmetic on floating point numbers is quite often not totally accurate. Try something like the following (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;
        }

    }

}

Your output should be something like:

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

And now you should be able to see why the condition d == 0 never happens. (the last number there is a number that is very close to 0 but not quite.

For another example of floating point weirdness, try this:

public class Squaring{

    public static void main(String[] args) {

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

    }

}

Because there is no binary representation of exactly 0.1, squaring it does not produce the result you would expect (0.01), but actually something like 0.010000000000000002!

撩发小公举 2024-09-19 06:14:25

f 未初始化;)

如果您的意思是:

double f = 2.0;

这可能是双变量上非精确算术的影响。

f is uninitialised ;)

If you mean:

double f = 2.0;

This can be a effect of non-precise arthimetic on double variables.

澉约 2024-09-19 06:14:25

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

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

}

it's because of the precision of floating point. use while (d>0.0), or if you must,

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

}
写给空气的情书 2024-09-19 06:14:25

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

As others have said, this is just a fundamental problem that you get when doing floating-point arithmetic to any base. It just happens that base-2 is the most common one in computers (because it admits efficient hardware implementation).

The best fix, if possible, is to switch to using some kind of quotient representation of the number for your looping, making the floating-point value be derived from that. OK, that sounds overblown! For your specific case, I'd write it as:

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

Here, we're really working with fractions [20/10, 18/10, 16/10, ..., 2/10, 0/10] where the iteration is done with integers (i.e., easy to get correct) in the numerator with a fixed denominator, before converting to floating-point. If you can rewrite your real iterations to work like this, you'll have great success (and they're really not much more expensive than what you were doing before anyway, which is a great trade-off to get correctness).

If you can't do this, you need use equal-within-epsilon as your comparison. Approximately, that's replacing d != target with abs(d - target) < ε, where ε (epsilon) selection can sometimes be awkward. Basically, the right value of ε depends on a bunch of factors, but it's probably best selected as 0.001 for the example iteration given the scale of the step value (i.e., it's half a percent of the magnitude of the step, so anything within that is going to be error instead of informative).

初相遇 2024-09-19 06:14:25

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

It doesn't stop because 0.2 i not precisely represented in two's complement
so your loop never executes the 0.0==0.0 test

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文