加载中…
个人资料
用户1639993743
用户1639993743
  • 博客等级:
  • 博客积分:0
  • 博客访问:589,953
  • 关注人气:489
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

[原创]MATLAB中关于0.1-0.3+0.2不等于0的解释

(2012-10-12 18:47:43)
标签:

matlab技术论坛

杂谈

分类: MATLAB
[原创]MATLAB中关于0.1-0.3+0.2不等于0的解释 

现在MATLAB的Command Window中进行一组运算
  1. >> 0.1+0.2-0.3

  2. ans =

  3.    5.5511e-17

  4. >> 0.1-0.3+0.2

  5. ans =

  6.    2.7756e-17
复制代码
为什么上式的结果不为0呢??且不同的运算顺序结果不一样呢??下面我们就详细解释这个原因!

在本教程之前推荐您先了解下《1985年IEEE发布了二进制浮点运算标准754-1985》。根据IEEE浮点数运算标准,我编写了两个简单的程序,用于ieee数值和double数值之间的转换。
  1. function [x_double,s,c,f]=ieee2double(x_ieee)
  2. % 将IEEE编码转换为双精度数据
  3. % x_double=(-1)^s*2^(c-1023)*(1+f),双精度数据
  4. % x_ieee,IEEE编码
  5. % s,符号位,长度1
  6. % c,指数位,长度11
  7. % f,尾数位,长度52
  8. %
  9. s=bin2dec(x_ieee(1));
  10. c=bin2dec(x_ieee(2:12));
  11. m=bin2dec(x_ieee(13:64)');
  12. % 为了保证精度,使用符号运算
  13. f=sym('1/2').^(1:52)*m;
  14. x_double=(-1)^s*2^(c-1023)*(1+f);
复制代码
  1. function [x_ieee,s,c,f]=double2ieee(x_double)
  2. % 将双精度数据转换为IEEE编码
  3. % x_double=(-1)^s*2^(c-1023)*(1+f),双精度数据
  4. % x_ieee,IEEE编码
  5. % s,符号位,长度1
  6. % c,指数位,长度11
  7. % f,尾数位,长度52

  8. if x_double>0
  9.     s='0';
  10. else
  11.     s='1';
  12. end
  13. n=floor(log2(x_double));
  14. c=dec2bin(n+1023,11);
  15. f=dec2bin(round((x_double/2^n-1)*2^52),52);
  16. x_ieee=[s,c,f];
复制代码
利用上面的double2ieee函数尝试得到0.1的IEEE编码
  1. >> x_double=0.1;
  2. >> x_ieee_01=double2ieee(x_double)

  3. x_ieee_01 =

  4. 0011111110111001100110011001100110011001100110011001100110011010
复制代码
也就是说0.1的IEEE编码就是上面那一坨0和1(晕吧),其实这串二进制代表的真实数据略大于0.1,也就是说
  1. IEEE(0011111110111001100110011001100110011001100110011001100110011001)
  2. <<br style="word-wrap: break-word; ">
  3. IEEE(0011111110111001100110011001100110011001100110011001100110011010)
复制代码
傻子都知道计算机是二进制存储数据的,由于0.1没有精确的IEEE编码,根据就近一致原则,0.1采用的IEEE编码就采用最近的第二个编码。

现在讨论下上面两个编码到底代表什么数据呢?好,使用ieee2double()函数来测试下
  1. >> x_double_01_left=ieee2double('0011111110111001100110011001100110011001100110011001100110011001')

  2. x_double_01_left =

  3. 7205759403792793/72057594037927936

  4. >> double(x_double_01_left)-0.1 % 看到没有,第一个IEEE编码和0.1还是有差距的

  5. ans =

  6.   -1.3878e-17

  7. >> x_double_01_right=ieee2double('0011111110111001100110011001100110011001100110011001100110011010')

  8. x_double_01_right =

  9. 3602879701896397/36028797018963968

  10. >> double(x_double_01_right)-0.1 % 第二个IEEE编码和0.1就没有区别了,但是第二个IEEE编码也不是0.1的真实编码,而是距离最近的一个,换句话说0.1是没有准确的IEEE编码的,当然还有很多数据也没有准确的IEEE编码

  11. ans =

  12.      0
复制代码
也就是说那一大串0和1对应于上面那两个分数(为了保留足够的精度,这里使用分数显示出来,如果直接采用小数显示,您不会看到区别的)!

同理可以得到0.2和0.3的IEEE编码,以及相应的IEEE编码代表的真实数值!
  1. % 0.1的编码转换
  2. >> x_ieee_01=double2ieee(0.1) % 0.1 IEEE编码

  3. x_ieee_01 =

  4. 0011111110111001100110011001100110011001100110011001100110011010

  5. >> x_double_01=ieee2double(x_ieee_01)

  6. x_double_01 =

  7. 3602879701896397/36028797018963968

  8. % 0.2的编码转换 
  9. >>  x_ieee_02=double2ieee(0.2) % 0.2 IEEE编码

  10. x_ieee_02 =

  11. 0011111111001001100110011001100110011001100110011001100110011010

  12. >>  x_double_02=ieee2double(x_ieee_02)

  13. x_double_02 =

  14. 3602879701896397/18014398509481984

  15. % 0.3的编码转换 
  16. >> x_ieee_03=double2ieee(0.3) % 0.3 IEEE编码

  17. x_ieee_03 =

  18. 0011111111010011001100110011001100110011001100110011001100110011

  19. >> x_double_03=ieee2double(x_ieee_03)

  20. x_double_03 =

  21. 5404319552844595/18014398509481984
复制代码
现在模拟计算0.1+0.3-0.2的结果
  1. >> x_double_01-x_double_03+x_double_02

  2. ans =

  3. 1/36028797018963968

  4. >> 1/36028797018963968

  5. ans =

  6.    2.7756e-17

  7. >> 0.1-0.3+0.2

  8. ans =

  9.    2.7756e-17
复制代码
也就是说在IEEE标准下,0.1+0.3-0.2的结果为1/36028797018963968≈2.7756e-17,显然这个不等于零!!

接下来讨论下,为什么0.1-0.3+0.2和0.1+0.2-0.3的结果不一样?

这个主要是由于加法运算是左结合的,也就是说0.1-0.3+0.2是先计算0.1-0.3,得到-0.2;而0.1+0.2-0.3是先计算0.1+0.2,得到0.3。-0.2和0.3的IEEE编码当然是不同的,相应的误差也有区别,于是得到最后结果也就不同了。至于具体多少,大家可以使用本文提供的两个函数进行测试和推到下!

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有