有效数字在增加

Significant digits increasing

本文关键字:增加 有效数字      更新时间:2023-10-16

让我们,

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

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

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

如果你在Harald Schmidt's excellent online converter中输入0.05,并引用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/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,因为它会强制刷新缓冲区。如果您的实现本身就在运行,那么当您发送换行符时,终端设备也会发生这种情况。如果您已将标准输出重定向到非终端,则可能不希望对每一行进行刷新。与你的问题无关,在绝大多数情况下可能不会有真正的区别,这只是我想提出的一点。

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

有些数字不能用浮点或基数为2的数字精确表示。如果我记得正确的话,其中一个数字是十进制的0.05(以2为基数导致小数无限重复)。另一个问题是,如果您将浮点打印到文件(以10为基数),然后将其读回,您可能会得到不同的数字,因为基数不同,这可能会在将小数基数2转换为小数基数10时造成问题。

如果你想要更高的精度,你可以尝试搜索bignum库。不过,这将比浮点运算慢得多。处理精度问题的另一种方法是尝试将数字存储为具有分母/分母的"公共分数"(即1/10而不是0.1,1/3而不是0.333.等-甚至可能有库,但我还没有听说过),但这对pie等无理数不起作用。