在循环中使用"double"作为计数器变量
Using "double" as counter variables in loops
在我目前正在读的一本书中,有这样一段摘录:
您也可以使用浮点值作为循环计数器。这是一个具有这种类型的
for
循环的示例计数器的:double a(0.3), b(2.5); for(double x = 0.0; x <= 2.0; x += 0.25) cout << "ntx = " << x << "ta*x + b = " << a*x + b;
此代码片段计算对于
x
的值,a*x+b
的值从0.0
到2.0
,步骤为0.25
;但是,你需要小心在中使用浮点计数器时循环。许多十进制值不能精确地用二进制表示浮点形式,因此存在差异可以累积价值。这意味着您不应该对for循环,以便结束循环取决于浮点循环计数器达到精确值。对于例如,以下设计不佳循环永不结束:for(double x = 0.0 ; x != 1.0 ; x += 0.2) cout << x;
这个循环的目的是当
x
的值变化时输出从CCD_ 8到CCD_;然而,0.2
没有作为二进制浮点值,因此CCD_ 11的值从来都不完全是CCD_。因此,第二环路控制表达式总是错误的,并且循环无限期地继续。
有人能解释一下第一个代码块是如何运行的,而第二个不运行吗?
即使x
没有完全达到2.0,第一个最终也会终止……因为它最终会比2.0大,从而爆发。
第二个必须使x
正好命中1.0才能断开。
不幸的是,第一个例子使用了0.25的步长,这正好可以用二进制浮点表示——如果两个例子都使用0.2作为步长,那会更明智。(0.2不能用二进制浮点精确表示。)
第一个块使用小于或等于条件(<=
)。
即使存在浮点不准确性,这最终也是错误的。
这是一个更广泛问题的例子-在比较双打时,您通常需要在一些可接受的容差内检查是否相等,而不是完全相等。
在某些情况下,通常检查未更改的默认值,相等是可以的:
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
}
浮点数在内部表示为二进制数,几乎总是采用IEEE格式。您可以在这里看到数字的表示方式:
http://babbage.cs.qc.edu/IEEE-754/
例如,二进制中的0.25是0.01b,表示为+1.00000000000000000000000*2-2。
这在内部存储,符号为1位,指数为8位(表示-127和+128之间的值),值为23位(前导1。未存储)。事实上,比特是:
[0][01111101][0000000000000000000000000]
而二进制中的0.2没有精确的表示,就像1/3在十进制中没有精确的表达一样。
这里的问题是,正如1/2可以精确地以十进制格式表示为0.5,但1/3只能近似为0.3333333333一样,0.25可以精确地表示为二进制分数,但0.2不能。在二进制中,它是0.0010011001100110011001100….b,其中最后四位重复。
要存储在计算机上,它被分组为0.0010011001100110011001101b。这是非常非常接近的,所以如果你在计算坐标或任何其他绝对值重要的东西,这很好。
不幸的是,如果您将该值加五次,您将得到1 00000000000000000000001b。(或者,如果你把0.2四舍五入到0.001001100110011001100b,你会得到0.11111111111111111100b)
无论哪种方式,如果循环条件为1 00000000000000000000001b==1 0000000000000000000000000000000b,它都不会终止。如果使用<=相反,如果值刚好低于最后一个值,它可能会额外运行一次,但它会停止。
可以制作一种能够准确表示小十进制值的格式(就像任何只有两位小数的值一样)。它们用于财务计算等。但正常的浮点值确实是这样工作的:它们用表示0.2等小"容易"数字的能力来换取以一致的方式表示大范围的能力。
出于这个原因,避免使用浮点作为循环计数器是很常见的,常见的解决方案是:
- 如果一个额外的迭代无关紧要,则使用<
- 如果它确实重要,则使条件<1.0001,或者比增量小的其他值,所以减去-0.0000000000000000000001的错误无关紧要
- 在循环过程中使用整数并将其除以
- 使用专门制作的类来精确地表示分数值
编译器可以优化float"="循环,将其转化为你想要发生的事情,但我不知道这是否是标准允许的,或者在实践中是否发生过。
该示例存在多个问题,不同情况下有两个不同之处。
-
涉及浮点等式的比较需要该领域的专家知识,因此使用
<
或>
进行循环控制更安全。 -
循环增量
0.25
实际上确实具有精确的表示 -
循环增量
0.2
是否不具有精确的表示 -
因此,可以精确检查许多0.25(或1.0)增量的总和,但即使是单个0.2增量也不可能精确匹配。
人们经常引用一条一般规则:不要对浮点数进行相等比较虽然这是一个很好的一般建议,但当处理整数或整数加上由½+¼组成的分数时。。。你可以期待精确的表示。
你问为什么?简短的答案是:因为分数表示为½+¼。。。,大多数十进制数字都没有精确的表示,因为它们不能被分解为二次幂。这意味着FP内部表示是将舍入到输出的预期值的长字符串,但实际上并不是该值。
一般做法是不比较两个浮点数,即:
// 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);
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 将数组的地址分配给变量并删除
- 为"adjacent"变量赋值时出现问题
- enum是C++中的宏变量还是整数变量
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- C++ 在循环中添加计数器变量并再次初始化其值
- C++计数器变量无法正常工作
- OPENCL计数器变量
- 意外的静态计数器变量行为
- 声明中的限定 ID 在"="令牌/对象计数器变量之前
- 为什么它不是一个不确定的循环,因为循环计数器在整数变量的范围之外
- 静态变量如何以及如何将其用作计数器
- 计数器变量错误
- 为什么只有一个锁和一个原子计数器的条件变量会错误地唤醒
- 如何将计数器变量从一个函数传递到main函数
- for循环需要运行LLONG_MAX次数,但是计数器变量也需要表示有符号的数字.你用什么类型的柜台?
- 双变量作为循环计数器
- 在循环中使用"double"作为计数器变量
- 循环和数组混淆:计数器变量