显着位数增加

Shi*_*bli 2 c++ significant-digits

让我们,

float dt;
Run Code Online (Sandbox Code Playgroud)

dt从一个文本文件中读取

inputFile >> dt;
Run Code Online (Sandbox Code Playgroud)

然后我有一个for循环,因为,

for (float time=dt; time<=maxTime; time+=dt)
{
    // some stuff
}
Run Code Online (Sandbox Code Playgroud)

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

0.05
0.10
...
7.00001
7.05001
...
Run Code Online (Sandbox Code Playgroud)

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

pax*_*blo 6

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

如果您输入0.05 Harald 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
Run Code Online (Sandbox Code Playgroud)

标志为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.15,0.2等可以表示两者都是.

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

#include <iostream>
#include <iomanip>
:
std::cout << std::setprecison (2) << time << '\n';
Run Code Online (Sandbox Code Playgroud)

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

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