找回密码
 注册
关于网站域名变更的通知
查看: 761|回复: 1
打印 上一主题 下一主题

MATLAB中关于0.1-0.3+0.2不等于0的解释

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-2-24 10:13 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x

: b- C; I8 \& ]6 Y  G现在MATLAB的Command Window中进行一组运算3 O8 W: m3 A8 G9 E2 J7 p0 Q
  • >> 0.1+0.2-0.3
  • ans =
  •   5.5511e-17
  • >> 0.1-0.3+0.2
  • ans =
  •   2.7756e-17
    , J" K8 f6 f0 H, g( W
( G' ]; [& ^& X5 k  ~% }7 e8 Z6 z

' X9 _4 ]: q& c* D2 `为什么上式的结果不为0呢??且不同的运算顺序结果不一样呢??下面我们就详细解释这个原因!
0 i* E6 L4 ~; l+ O- x) d; E
9 B1 H, |4 u* N0 g- r9 G在本教程之前推荐您先了解下《1985年IEEE发布了二进制浮点运算标准754-1985》。根据IEEE浮点数运算标准,我编写了两个简单的程序,用于ieee数值和double数值之间的转换。
4 d6 l9 ?$ ], `& }4 S/ U  J
  • function [x_double,s,c,f]=ieee2double(x_ieee)
  • % 将IEEE编码转换为双精度数据
  • % x_double=(-1)^s*2^(c-1023)*(1+f),双精度数据
  • % x_ieee,IEEE编码
  • % s,符号位,长度1
  • % c,指数位,长度11
  • % f,尾数位,长度52
  • %
  • s=bin2dec(x_ieee(1));
  • c=bin2dec(x_ieee(2:12));
  • m=bin2dec(x_ieee(13:64)');
  • % 为了保证精度,使用符号运算
  • f=sym('1/2').^(1:52)*m;
  • x_double=(-1)^s*2^(c-1023)*(1+f);
      h5 t8 x0 g7 d$ u3 P. r: |
6 J, @) a) g/ P1 A& l, e  E' Y( f
9 |7 s- Y$ O% G. `
  • function [x_ieee,s,c,f]=double2ieee(x_double)
  • % 将双精度数据转换为IEEE编码
  • % x_double=(-1)^s*2^(c-1023)*(1+f),双精度数据
  • % x_ieee,IEEE编码
  • % s,符号位,长度1
  • % c,指数位,长度11
  • % f,尾数位,长度52
  • if x_double>0
  •    s='0';
  • else
  •    s='1';
  • end
  • n=floor(log2(x_double));
  • c=dec2bin(n+1023,11);
  • f=dec2bin(round((x_double/2^n-1)*2^52),52);
  • x_ieee=[s,c,f];
    , G* p, a4 Y) Y9 o% b
3 D! U2 N: X  Q
' E. K$ ?1 G/ U4 ^2 ?7 f
利用上面的double2ieee函数尝试得到0.1的IEEE编码
; U2 k8 X+ l' V( O" X
  • >> x_double=0.1;
  • >> x_ieee_01=double2ieee(x_double)
  • x_ieee_01 =
  • 0011111110111001100110011001100110011001100110011001100110011010
    $ z/ F  W1 D* G; y) d* j
6 h' T. l+ [' B$ R; ^: ?* n. m

8 _- ?& g6 [; L; X也就是说0.1的IEEE编码就是上面那一坨0和1(晕吧),其实这串二进制代表的真实数据略大于0.1,也就是说
* ]; I$ ]; v* G1 O8 [
  • IEEE(0011111110111001100110011001100110011001100110011001100110011001)
  • <<br style="word-wrap: break-word; ">
  • IEEE(0011111110111001100110011001100110011001100110011001100110011010)
    8 s2 i, @4 J: C* S$ s

# f5 I7 A& n8 n1 Z
% g/ G" q2 N. t2 R' J( s傻子都知道计算机是二进制存储数据的,由于0.1没有精确的IEEE编码,根据就近一致原则,0.1采用的IEEE编码就采用最近的第二个编码。+ }9 [& H7 s$ k7 x2 l

# f( O( z3 Q7 q4 W现在讨论下上面两个编码到底代表什么数据呢?好,使用ieee2double()函数来测试下
; Z( h2 Y) c  ?
  • >> x_double_01_left=ieee2double('0011111110111001100110011001100110011001100110011001100110011001')
  • x_double_01_left =
  • 7205759403792793/72057594037927936
  • >> double(x_double_01_left)-0.1 % 看到没有,第一个IEEE编码和0.1还是有差距的
  • ans =
  •   -1.3878e-17
  • >> x_double_01_right=ieee2double('0011111110111001100110011001100110011001100110011001100110011010')
  • x_double_01_right =
  • 3602879701896397/36028797018963968
  • >> double(x_double_01_right)-0.1 % 第二个IEEE编码和0.1就没有区别了,但是第二个IEEE编码也不是0.1的真实编码,而是距离最近的一个,换句话说0.1是没有准确的IEEE编码的,当然还有很多数据也没有准确的IEEE编码
  • ans =
  •     0
    ' i# ~2 l! }3 F% e
6 C' G7 S' V3 R3 U: ~$ p
9 v0 ]( v) e* ~7 [, Z' a
也就是说那一大串0和1对应于上面那两个分数(为了保留足够的精度,这里使用分数显示出来,如果直接采用小数显示,您不会看到区别的)!' w' O2 Y& C' z! j2 u5 P# z6 x

" b' N9 c  n" e! ~同理可以得到0.2和0.3的IEEE编码,以及相应的IEEE编码代表的真实数值!" \1 O* B- A8 y# I& |2 h& ^/ r/ h
  • % 0.1的编码转换
  • >> x_ieee_01=double2ieee(0.1) % 0.1 IEEE编码
  • x_ieee_01 =
  • 0011111110111001100110011001100110011001100110011001100110011010
  • >> x_double_01=ieee2double(x_ieee_01)
  • x_double_01 =
  • 3602879701896397/36028797018963968
  • % 0.2的编码转换
  • >>  x_ieee_02=double2ieee(0.2) % 0.2 IEEE编码
  • x_ieee_02 =
  • 0011111111001001100110011001100110011001100110011001100110011010
  • >>  x_double_02=ieee2double(x_ieee_02)
  • x_double_02 =
  • 3602879701896397/18014398509481984
  • % 0.3的编码转换
  • >> x_ieee_03=double2ieee(0.3) % 0.3 IEEE编码
  • x_ieee_03 =
  • 0011111111010011001100110011001100110011001100110011001100110011
  • >> x_double_03=ieee2double(x_ieee_03)
  • x_double_03 =
  • 5404319552844595/18014398509481984
    + J7 _, Z3 O7 X/ I' ?
8 f  g. L5 ~) T+ k, v7 r8 N+ b5 x1 y1 |
! Y! F5 t; i: W6 z+ ^# j9 ^
现在模拟计算0.1+0.3-0.2的结果
8 Y; {! Z+ R6 I
  • >> x_double_01-x_double_03+x_double_02
  • ans =
  • 1/36028797018963968
  • >> 1/36028797018963968
  • ans =
  •   2.7756e-17
  • >> 0.1-0.3+0.2
  • ans =
  •   2.7756e-17+ j  p( }3 t, \$ M. _: {
+ v0 c) B1 k9 e/ z4 i7 G1 q2 x5 R
8 N$ ^# P* j* M8 ~8 B  o$ ^  t; x& x
也就是说在IEEE标准下,0.1+0.3-0.2的结果为1/36028797018963968≈2.7756e-17,显然这个不等于零!!# m$ Z. R' @2 @) l* o3 f1 e

9 s  P3 f7 e$ I6 I* p6 Z" Y8 {接下来讨论下,为什么0.1-0.3+0.2和0.1+0.2-0.3的结果不一样?
+ z; d8 p( a8 X5 @. K. T- r0 t5 W6 \% Y# b9 x4 u  S/ A6 d
这个主要是由于加法运算是左结合的,也就是说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编码当然是不同的,相应的误差也有区别,于是得到最后结果也就不同了。至于具体多少,大家可以使用本文提供的两个函数进行测试和推到下!

该用户从未签到

2#
发表于 2020-2-24 17:41 | 只看该作者
MATLAB中关于0.1-0.3+0.2不等于0的解释
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-11-24 11:20 , Processed in 0.140625 second(s), 24 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表