2

让我们,

float dt;

dt从文本文件中读取为

inputFile >> dt;

然后我有一个for循环,

for (float time=dt; time<=maxTime; time+=dt)
{
    // some stuff
}

dt=0.05我输出std::cout << time << std::endl;我得到的时候,

0.05
0.10
...
7.00001
7.05001
...

那么,为什么一段时间后位数会增加?

4

3 回答 3

6

因为不是每个数字都可以用 IEEE754 浮点值表示。在某些时候,您会得到一个不太具有代表性的数字,计算机将不得不选择最接近的数字。

如果您输入 0.05Harald Schmidt's excellent online converter并参考IEEE754-1985 上的维基百科条目,您将得到以下位(我的解释如下):

   s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
   0 01111010 10011001100110011001101
     |||||||| |||||||||||||||||||||||
128 -+||||||| ||||||||||||||||||||||+- 1 / 8388608
 64 --+|||||| |||||||||||||||||||||+-- 1 / 4194304
 32 ---+||||| ||||||||||||||||||||+--- 1 / 2097152
 16 ----+|||| |||||||||||||||||||+---- 1 / 1048576
  8 -----+||| ||||||||||||||||||+----- 1 /  524288
  4 ------+|| |||||||||||||||||+------ 1 /  262144
  2 -------+| ||||||||||||||||+------- 1 /  131072
  1 --------+ |||||||||||||||+-------- 1 /   65536
              ||||||||||||||+--------- 1 /   32768
              |||||||||||||+---------- 1 /   16384
              ||||||||||||+----------- 1 /    8192
              |||||||||||+------------ 1 /    4096
              ||||||||||+------------- 1 /    2048
              |||||||||+-------------- 1 /    1024
              ||||||||+--------------- 1 /     512
              |||||||+---------------- 1 /     256
              ||||||+----------------- 1 /     128
              |||||+------------------ 1 /      64
              ||||+------------------- 1 /      32
              |||+-------------------- 1 /      16
              ||+--------------------- 1 /       8
              |+---------------------- 1 /       4
              +----------------------- 1 /       2

符号为 0,为正。指数由映射到左侧数字的一位表示:64+32+16+8+2 = 122 - 127 bias = -5,因此乘数为 2 -51/32。偏差是允许表示非常小的127数字(如接近零而不是大的负数)。

尾数稍微复杂一些。对于每一个位,您在右侧累积数字(在添加隐含之后1)。因此,您可以将数字计算为 的总和{1, 1/2, 1/16, 1/32, 1/256, 1/512, 1/4096, 1/8192, 1/65536, 1/131072, 1/1048576, 1/2097152, 1/8388608}

当你把所有这些加起来时,你得到1.60000002384185791015625.

当您将乘以乘数1/32(之前从指数位计算)时,您会得到0.0500000001,因此您可以看到它0.05已经没有精确表示。尾数的这个位模式实际上是相同的,0.1但是,指数是-4而不是-5,这就是为什么0.1 + 0.1 + 0.1很少等于0.3(这似乎是一个最喜欢的面试问题)。

当你开始把它们加起来时,那个小错误会累积起来,因为你不仅会看到它本身的错误,0.05而且在累积的每个阶段也可能会引入错误——并不是所有的数字0.1,等等都可以表示出来确切地说。0.150.2

最终,如果您使用默认精度,错误将变得足够大,以至于它们将开始显示在数字中。您可以通过以下方式选择自己的精度来推迟此操作:

#include <iostream>
#include <iomanip>
:
std::cout << std::setprecison (2) << time << '\n';

它不会修复变量值,但会在错误变得可见之前给您更多的喘息空间。

顺便说一句,有些人建议避免,std::endl因为它会强制刷新缓冲区。如果您的实现自己运行,那么当您发送换行符时,终端设备就会发生这种情况。而且,如果您已将标准输出重定向到非终端,您可能希望在每一行上都刷新。与您的问题并不真正相关,并且在绝大多数情况下可能不会产生真正的影响,这只是我想我会提出的一点。

于 2012-02-25T11:35:14.110 回答
3

IEEE 浮点数使用二进制数系统,因此无法准确存储十进制数。当您将其中的几个加在一起时(有时只需两个就足够了),代表性错误会累积并变得可见。

于 2012-02-25T11:33:58.163 回答
0

某些数字无法使用浮点数或以 2 为底的数字精确表示。如果我没记错的话,其中一个数字是十进制 0.05(以 2 为底会导致无限重复的小数)。另一个问题是,如果您将浮点数打印到文件(作为基数 10 数字)然后读回它,您可能会得到不同的数字 - 因为基数不同,并且在将小数 base2 转换为小数 base10 数字时可能会导致问题。

如果您想要更好的精度,您可以尝试搜索 bignum 库。不过,这将比浮点数慢得多。处理精度问题的另一种方法是尝试将数字存储为带有数字/分母的“共同分数”(即 1/10 而不是 0.1、1/3 而不是 0.333 ......等等 - 甚至可能有库,但是我还没有听说过),但这不适用于像pie这样的无理数。

于 2012-02-25T11:59:45.960 回答