浮点数在计算机编程中一定会使用,然而使用中会有很多坑。最近做机械臂就被跳到坑里了。机械臂三自由度反解出来的cos值比1大一点点,1.00000...4。就是这一点点,坑了我一晚上,坑了师兄一上午。计算公式没问题,求解程序没问题,就是算出来的值有问题!后来才反应到是浮点数存储造成的。
举一个最简单的浮点数存储影响的例子。
double targetValue = 0.21; double counter = 0; int main() { while(counter != targetValue) { counter += 0.01; } return 0; }
上面的程序,按照我们想想的,while循环将执行21次,之后counter和targetValue相等,循环结束。
但是事实上,运行的时候并不是这样。这里我截取几个vs调试的变量值来说明。
首次进入循环前,可以看到targetValue的值并不是我们设置的0.21,而是0.20999...
在每次相加时,counter也不是加0.01,而是和0.01差距很小的一个数,经过20次相加之后并不是0.2,而有差距。
当相加21次时,两个数并不相等,所以无法退出循环。
可以看出,浮点数并不如我们认为的那么精确。实际上,造成这个的原因是因为计算机的存储机制造成的。计算机使用二进制存储数据,那么对于0.21这个数,下面是转换的过程,乘二取整法。
0.21*2=0.42 整数为0 -> 0.0
0.42*2=0.84 整数为0 -> 0.00
0.84*2=1.68 整数为1 -> 0.001
0.68*2=1.36 整数为1 -> 0.0011
0.36*2=0.72 整数为0 -> 0.00110
最后可以表示为:
0.0011010111000010…
可以看出,转换成二进制之后,是一个无限的数,不能精确的表示出来。所以就造成了这个bug。这也是我们程序中,cos算出来超过1一点点的原因。我在matlab里还原了c++的算法,matlab计算出的数值就是1,没有任何小数。
对于上面比较的问题,很多人都知道解决方法,那就是两个浮点数求差,差值很小就认为相等。程序如下:
double targetValue = 0.21; double counter = 0; int main() { while(fabs(counter - targetValue) > 1e-5) { counter += 0.01; } return 0; }
关于cos计算的bug,我最后采取的手段是将浮点数保留5位小数,精度能够符合我们的要求,而且不会被浮点数存储影响。
计算机上的坑真的多,争取毕业前多踩,多填坑。