前言
对于精度误差这个词的认识是在看别人写的sqrt函数应用时开始的,现在并没有在这方面遇到过问题,下面的经验是对所看过这一类文章的总结
一、什么是精度误差
比如这里我们用一个double去接收一个六位精度的值,那么前六位数的值一定是固定的,但是六位以后的值由于没有固定的输入将会是任意可能出现的数值。同理我们如果存入0给double在0以后位数的值也都是不确定的,这种不确定位数的值不可控的现象就是精度误差。
二、精度误差事例即解决方法
1、sqrt判断质数
一般的判断质数的代码都是如下的,我一开始也是这么写的。
1. bool find(int n) 2. { 3. for(int i=2;i<=sqrt(n);i++) 4. { 5. if(n%i==0) 6. return false; 7. } 8. return true; 9. }
但这样的写法其实是不严谨的,比如说我判断9是不是素数,这时候sqrt(9)的值本应该是3,但其实sqrt的返回值是一个浮点数,所以这里的精度误差就可能导致返回值为2.9999999这一类数,从而导致答案的错误,正确的写法应该如下。
1. bool find(int n) 2. { 3. int sql=(int)sqrt(1.0*n);//1.0*n的目的是 隐式转换成浮点数,开根号后再强制转换成整型 4. for(int i=2;i<=sql;i++) 5. { 6. if(n%i==0) 7. return false; 8. } 9. return true; 10. }
2、判断浮点数相等
即使是理论上相同的值,由于是经过不同的运算过程得到的,他们在低几位有可能(一般来说都是)是不同的。这种现象看似没太大的影响,却会一种运算产生致命的影响: ==。注意,C/C++中浮点数的==需要完全一样才能返回true,解决的办法是引进eps,来辅助判断浮点数的相等。
eps缩写自epsilon,表示一个小量,但这个小量又要确保远大于浮点运算结果的不确定量。eps最常见的取值是1e-8左右。引入eps后,我们判断两浮点数a、b相等的方式如下
这样,我们才能把相差非常近的浮点数判为相等;同时把相差较大(差值大于eps)的数判为不相等。
3、eps解决函数越界
如果sqrt(a), asin(a), acos(a) 中的a是你自己算出来并传进来的,那么就有可能因为浮点误差导致出现错误。如果a本来应该是0的,由于浮点误差,可能实际存储的是一个绝对值很小的负数(比如1e-12),这样sqrt(a)应得0的,直接因a不在定义域而出错。
类似地,如果a本来应该是±1,则asin(a)、acos(a)也有可能出错,对于此种函数,必需事先对a进行校正。
4、eps解决输出问题
这一节都是因为题目要求输出浮点数导致的问题,并且都和四舍五入有关。
现在考虑一种情况,题目要求输出保留两位小数。case的正确答案的精确值是0.005,按理应该输出0.01,但你的结果可能是0.005000000001(恭喜),也有可能是0.004999999999(悲剧),如果按照printf(“%.2lf”, a)输出,那你的遭遇将和括号里的字相同。
解决办法是如果a为正,则输出a+eps, 否则输出a-eps
5、 输入值波动过大
假如一道题输入给一个浮点数a, 1e-20 < a < 1e20。这时候就不要再用1e-8左右作为eps的值了,合理的做法是把eps按照输入规模缩放到合适大小。
总结
在对set进行判重的时候,是不是用==来判重的?经观察,set不是通过==来判断相等的,是通过<来进行的,具体说来,只要a<b 和 b<a 都不成立,就认为a和b相等,可以发现,精度误差问题可以用这个方法或者eps很好的解决的。容易产生较大浮点误差的函数有asin、 acos,所以尽量使用atan2。另外,如果数据明确说明是整数,而且范围不大的话,使用int或者long long代替double都是极佳选择.