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

afa*_*lek 45 c# c++ floating-point counter loops

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

您还可以使用浮点值作为循环计数器.这是一个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;
Run Code Online (Sandbox Code Playgroud)

此代码片段计算的值a*x+b对于值x0.02.0,在步骤 0.25; 但是,在循环中使用浮点计数器时需要注意.许多十进制值无法以二进制浮点形式精确表示,因此差异可能会累积为累积值.这意味着您不应该编写for循环,以便结束循环取决于浮点循环计数器达到精确值.例如,以下设计不良的循环永远不会结束:

for(double x = 0.0 ; x != 1.0 ; x += 0.2)
    cout << x;
Run Code Online (Sandbox Code Playgroud)

与此环的目的是要输出的值x,因为它从变化0.01.0; 但是,0.2 没有精确表示为二进制浮点值,因此值x绝不是完全正确的1.因此,第二个循环控制表达式始终为false,并且循环无限期地继续.

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

Jon*_*eet 71

第一个将最终终止,即使x没有达到准确 2.0 ...因为它最终会成为更大小于2.0,从而打破了.

第二个必须x使命中正好 1.0以便打破.

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

  • 和.浮动的'a == b`的传统替代是`abs(ab)<epsilon`其中`epsilon`小于预期的精度.例如,如果所有使用的数字都不精确到0.01,则"epsilon = 0.001". (17认同)
  • @Pavel,因为多次添加浮点数时错误累积,即使对epsilon进行测试也可能存在风险.错误可能比您预期的要大.此外,它只是简单的丑陋. (3认同)

SLa*_*aks 15

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

即使浮点不准确,最终也会是错误的.


Ste*_*end 9

这是一个更广泛问题的例子 - 在比较双精度时,您经常需要在一些可接受的容差范围内检查相等性,而不是精确相等.

在某些情况下,通常检查未更改的默认值,相等是正常的:

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

if (x != 0.0) {   
    // do more work 
}
Run Code Online (Sandbox Code Playgroud)

一般来说,检查与预期值不能这样做 - 你需要这样的东西:

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 
}
Run Code Online (Sandbox Code Playgroud)


Jac*_* V. 6

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

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

例如,0.25的二进制是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它将不会终止.如果你使用<=代替,如果值刚好在最后一个值之下,它可能会运行一个额外的时间,但它会停止.

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

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

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

编译器可以优化浮动"="循环以将其转换为您的意思,但我不知道标准是允许的还是在实践中发生的.