在循环中使用“double”作为计数器变量

时间:2011-06-30 14:24:07

标签: c# c++ loops floating-point counter

在我正在阅读的一本书中,有一段摘录:

  

您也可以使用浮点数   值作为循环计数器。这是一个   这种for循环的示例   柜台:

double a(0.3), b(2.5);
for(double x = 0.0; x <= 2.0; x += 0.25)
    cout << "\n\tx = " << x << "\ta*x + b = " << a*x + b;
     

此代码片段计算   a*x+b的值为x   从0.02.0,步骤为   0.25;但是,你需要小心   当使用浮点计数器时   一个循环。许多小数值不能   完全用二进制表示   浮点形式,如此不符   可以积累累积值。   这意味着你不应该编码   for循环,以便结束循环   取决于浮点循环   计数器达到精确值。对于   例如,以下设计不佳   循环永远不会结束:

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    cout << x;
     

这个循环的意图是   输出x的值,因为它变化   从0.01.0;但是,0.2   没有确切的表示形式   二进制浮点值,所以   x的值绝不是1。   因此,第二个循环控制   表达总是假的,而且   循环无限期地继续。

有人可以解释第一个代码块如何运行而第二个代码块不运行?

6 个答案:

答案 0 :(得分:71)

第一个将最终终止,即使x未达到完全 2.0 ...因为它最终将更大而不是2.0 ,然后爆发。

第二个必须让x点击完全 1.0才能中断。

令人遗憾的是,第一个示例使用0.25的步长,这在二进制浮点中是完全可表示的 - 如果两个示例都使用0.2作为步长,那将更为明智。 (0.2在二进制浮点中不能完全表示。)

答案 1 :(得分:15)

第一个块使用小于或等于的条件(<=)。

即使浮点不准确,最终也会出现错误。

答案 2 :(得分:9)

这是一个更广泛问题的例子 - 在比较双打时,您经常需要在某个可接受的容差内检查等式而不是完全相等。

在某些情况下,通常检查未更改的默认值,相等就可以了:

double x(0.0); 
// do some work that may or may not set up x

if (x != 0.0) {   
    // do more work 
}

一般情况下,检查与预期值无法以这种方式进行 - 您需要以下内容:

double x(0.0); 
double target(10000.0);
double tolerance(0.000001);
// do some work that may or may not set up x to an expected value

if (fabs(target - x) < tolerance) {   
    // do more work 
}

答案 3 :(得分:6)

浮点数在内部表示为二进制数,几乎总是以IEEE格式表示您可以在此处查看数字的表示方式:

http://babbage.cs.qc.edu/IEEE-754/

例如,0.25 in binary是0.01 b ,表示为+1.00000000000000000000000 * 2 -2

这内部存储有1位用于符号,8位用于指数(表示值介于-127和+128之间,23位用于值(前导1.未存储)。事实上,位是:

[0] [01111101] [00000000000000000000000]

而二进制中的0.2没有精确的表示,就像1/3没有十进制的精确表示一样。

这里的问题是正如1/2可以精确地以十进制格式表示为0.5,但是1/3只能近似为0.3333333333,0.25可以精确地表示为二进制分数,但0.2不能。在二进制文件中,它是0.0010011001100110011001100 .... b ,其中最后四位数字重复。

要存储在计算机上,请将其设置为0.0010011001100110011001101 b 。这真的非常接近,所以如果你在计算坐标或其他绝对值很重要的东西,那就没关系了。

不幸的是,如果您将该值添加到自身五次,您将获得1.00000000000000000000001 b 。 (或者,如果您将0.2舍入到0.0010011001100110011001100 b ,则会得到0.11111111111111111111100 b

无论哪种方式,如果您的循环条件是1.00000000000000000000001 b == 1.00000000000000000000000 b ,它将不会终止。如果你使用&lt; =代替它,如果值刚好在最后一个值之下,它可能会运行一个额外的时间,但它会停止。

可以制作一种能够准确表示小十进制值的格式(就像只有两个小数位的任何值一样)。它们用于财务计算等。但正常的浮点值确实如此:它们交换能够表示一些小的“简单”数字,如0.2,以便能够以一致的方式表示大范围。

出于某种原因,通常避免使用float作为循环计数器,常见的解决方案是:

  • 如果一次额外的迭代无关紧要,请使用&lt; =
  • 如果确实重要,请改为条件&lt; = 1.0001,或者小于增量的其他值,所以0.0000000000000000000001错误无关紧要
  • 使用整数并在循环期间将其除以
  • 使用专门用于表示小数值的类

编译器可以优化浮动“=”循环以将其转换为您的意思,但我不知道标准是否允许这种情况,或者在实践中是否会发生这种情况。

答案 4 :(得分:2)

示例存在多个问题,案例之间有两个不同。

  • 涉及浮点平等的比较需要域的专业知识,因此使用<>进行循环控制会更安全。

  • 实际上 的循环增量0.25 具有精确的表示

  • 循环增量0.2 具有精确表示

  • 因此,可以准确检查多个 0.25 (或 1.0 )增量的总和,但即使是单个也不可能完全匹配 0.2 增量。

通常引用一般规则:不要对浮点数进行相等比较。虽然这是一个很好的一般建议,但在处理整数或整数加上由½+¼组成的分数时。你可以期待准确的陈述。

你问为什么?简短的回答是:因为分数表示为½+¼...,大多数十进制数字没有精确的表示,因为它们不能被分解为2的幂。这意味着FP内部表示是长字符串,它将舍入到输出的预期值,但实际上 不是那个值。

答案 5 :(得分:1)

一般的做法是你不比较两个浮点数,即:

// using System.Diagnostics;

double a = 0.2; a *= 5.0;
double b = 1.0;
Debug.Assert(a == b);

由于浮点数不精确,a可能 等于b。要比较相等性,您可以将两个数字的差异与容差值进行比较:

Debug.Assert(Math.Abs(a - b) < 0.0001);