可能重复:
在 C# 中比较双精度值时出现问题
我在其他地方读过,但真的忘记了答案,所以我再次在这里问。这个循环似乎永远不会结束,无论你用任何语言编写它(我用 C#、C++、Java... 测试它):
double d = 2.0;
while(d != 0.0){
d = d - 0.2;
}
可能重复:
在 C# 中比较双精度值时出现问题
我在其他地方读过,但真的忘记了答案,所以我再次在这里问。这个循环似乎永远不会结束,无论你用任何语言编写它(我用 C#、C++、Java... 测试它):
double d = 2.0;
while(d != 0.0){
d = d - 0.2;
}
浮点计算并不完全精确。您将收到表示错误,因为 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
。
(一方面你没有使用相同的变量,但我认为这是一个错字:)
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;
}
在这里,“三分之一”不能完全表示,所以我们最终会遇到与以前相同的问题。
但总的来说,在浮点数之间执行精确相等比较是一个坏主意——通常你在一定的公差范围内比较它们。
我记得我买了一台 Sinclair ZX-81,读完优秀的 Basic 编程手册,当我遇到第一个浮点舍入错误时,我差点回到商店。
我从来没有想过人们在 27.99998 年后仍然会遇到这些问题。
你最好使用
while(f > 0.0)
*编辑:见下面帕斯卡的评论。但是,如果您确实需要以整数的、确定的次数运行循环,请使用整数数据类型。
问题是浮点运算。如果一个数字没有精确的二进制表示,那么你只能存储最接近它的数字(就像你不能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
!
f 未初始化;)
如果你的意思是:
double f = 2.0;
这可能是对双变量的非精确算法的影响。
这是因为浮点的精度。使用 while (d>0.0),或者如果必须,
while (Math.abs(d-0.0) > some_small_value){
}
它不会停止,因为 0.2 我没有用二进制补码精确表示,所以你的循环永远不会执行0.0==0.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 != target
为abs(d - target) < ε
,其中 ε (epsilon) 选择有时会很尴尬。基本上,ε 的正确值取决于一系列因素,但对于示例迭代,考虑到步长值的比例,它可能最好选择为 0.001(即,它是步长幅度的 0.5%,所以在此范围内的任何值将是错误而不是信息)。