在我正在阅读的一本书中,有一段摘录:
您也可以使用浮点数 值作为循环计数器。这是一个 这种
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.0
到2.0
,步骤为0.25
;但是,你需要小心 当使用浮点计数器时 一个循环。许多小数值不能 完全用二进制表示 浮点形式,如此不符 可以积累累积值。 这意味着你不应该编码 for循环,以便结束循环 取决于浮点循环 计数器达到精确值。对于 例如,以下设计不佳 循环永远不会结束:for(double x = 0.0 ; x != 1.0 ; x += 0.2) cout << x;
这个循环的意图是 输出
x
的值,因为它变化 从0.0
到1.0
;但是,0.2
没有确切的表示形式 二进制浮点值,所以x
的值绝不是1
。 因此,第二个循环控制 表达总是假的,而且 循环无限期地继续。
有人可以解释第一个代码块如何运行而第二个代码块不运行?
答案 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作为循环计数器,常见的解决方案是:
编译器可以优化浮动“=”循环以将其转换为您的意思,但我不知道标准是否允许这种情况,或者在实践中是否会发生这种情况。
答案 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);