显着位数增加

时间:2012-02-25 11:32:08

标签: c++ significant-digits

让的,

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
...

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

3 个答案:

答案 0 :(得分:6)

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

如果您在Harald Schmidt's excellent online converter中输入0.05并引用Wikipedia entry on 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 -5 1/32127偏差是允许表示非常小的数字(如接近于零,而不是具有大幅度的负数)。

尾数稍微复杂一些。对于每一位,您在右侧累积数字(在添加隐式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.10.150.2等可以完全表示。

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

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

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

  

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

答案 1 :(得分:3)

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

答案 2 :(得分:0)

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

如果您想要更好的精确度,可以尝试搜索bignum库。不过,这比浮点运行速度慢得多。处理精度问题的另一种方法是尝试将数字存储为&#34;普通分数&#34;与numberator / denominator(即1/10而不是0.1,1 / 3而不是0.333 ..等等 - 甚至可能是图书馆的库,但我还没有听说过),但是赢得了#39;使用非理性数字,例如 pi e