让我们,

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浮点值表示。在某些时候,你会得到一个不太可表示的数字,计算机将不得不选择最近的数字。

如果你输入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

符号为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';

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

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

其他提示

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

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

如果你想要更好的精度,你可以尝试搜索一个bignum库。不过,这将比浮点慢得多。处理精度问题的另一种方法是尝试将数字存储为带有numberator/分母(即1/10而不是0.1,1/3而不是0.333。.,等等-甚至可能有图书馆,但我还没有听说过),但这对无理数不起作用,比如 圆周率e.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top