关于float与double结果的误差
关于float与double结果的误差2007-09-30 10:41假设有一道题:
# _: }# d& k0 f5 w1 r5 m
- F# f9 R8 T) B( c! ^
float a;
# Z4 z5 V& }2 j; C a=123456.789e5; 0 r+ G1 e1 @1 \% i, ?* f8 V
System.out.printf("%f\n",a); # I8 v+ K3 F. ~( y7 n: ]
, u2 j6 y5 B7 H: l% g# a
结果是: ' p5 @$ j- K4 q
9 h+ a' b) v# z" R% J# s+ v; {
12345678848.000000 6 @9 U8 s5 F& ]$ O1 v( ]
; c* R+ `% x$ C
" g, K+ I \- u
而不是: 12345678900.000000 & m0 S! v7 v0 x7 \' P- }
- I; B+ F2 ^# |% P% l
# J8 P8 ?9 O7 E8 r% |! @5 a " a3 u/ e" e- f
分析:由于float在内存中占4个字节(32 bit),对于这个数来说会有数据丢失。实型数据是按照指数形式存储的。系统把一个实型数据分成小数部分和整数部分分别存放。在4个字节中,究竟用多少位来表示小数部分,多少位来表示指数部分,对于不同编译器可能会有不同。多数是以3 byte (24 bit) 表示小数部分(包括符号),以1 byte (8 bit) 表示指数部分(包括指数的符号)。 4 q; s2 `/ O$ b" m* h7 i
- {% S I3 V& r9 C
看看浮点数是怎样炼成的,呵呵: ( M) R$ P$ K" b) Y: {& ]+ w' o
3 T) s# V) _2 V1 \12345678900(十进制)=>
) e) G/ ~/ V! q/ T
% a. n: D- g8 B+ @1011011111110111000001110000110100(34位精确值) 5 X, U' _9 P4 _) a) W
( O$ F& m7 ^& W3 r9 e) G! p, I$ Q========>
# C" P0 w2 Q' U- F$ b; Y0 `! l
4 y/ V: D$ x I9 k8 c$ G符号位:0
' e* L4 ^% G; F: I' S +127
1 k! c% @1 q1 p8 n: U; @指数:33(100001=>00100001 =====> 10100000 5 Z' [: K6 B+ r g: }1 N
原码 阶码(移码)
1 b+ N3 t- i- e* |* I. x5 n& y2 F
- x" ~4 }: M+ y7 E: i尾数:1.01101111111011100000111(取24位)
5 P: @' C( @2 z- Z' ?( K1 A , J4 N4 j) y% b1 u9 O0 r
=>(注意:前面的1在实际存放时为了多存放一位而隐含,即浮点数的尾数的最高位永远隐含为1)
1 `, p/ a" `; `0 10100000 01101111111011100000111(实际放了尾数后面的23位)
; b8 X; P H2 K3 D0 " [ a 指数 尾数 0 S- O, w o' E; \
7 Y6 X7 X$ J4 S1 o) L7 a( h最后结果就是01010000001101111111011100000111
# ]- a: h2 o" s9 `/ A 1 x" E1 }; _5 C
现在再把它还原成整数:
/ Y. g. @; Q7 x& J+ w/ v P' C(1)取尾数23位:01101111111011100000111 : v% l& y, H* |& N; M, X) `- z
(2)在前面加上隐含的1,变成:101101111111011100000111
+ H: X: K, M' M/ i3 [: ^(3)取指数8位:10100000 2 [- ~6 G$ K0 i" C) p; b6 ]- }8 W
(4)指数减127得:100001(33)
! F- Z- m' A6 V# p6 ^(5)尾数向左移动10位(尾数本身23位,33-23=10):1011011111110111000001110000000000
6 g. i' s! K; g' b4 r此即12345678848 + H: V/ `) [' {! y* v
( c$ y) [: J3 Y$ a
) G3 q, d/ Z! b& _9 L& J' d" S! l' S
6 B* v$ Y. [7 N9 d; @启示:在需要精确答案的地方,要避免使用float和double;对于货币计算,要使用int、long(把零钱换成分来表示,这时就没有小数位,然后结果再除以相应的权值.)或BigDecimal。
- F# f9 R8 T) B( c! ^
float a;
# Z4 z5 V& }2 j; C a=123456.789e5; 0 r+ G1 e1 @1 \% i, ?* f8 V
System.out.printf("%f\n",a); # I8 v+ K3 F. ~( y7 n: ]
, u2 j6 y5 B7 H: l% g# a
结果是: ' p5 @$ j- K4 q
9 h+ a' b) v# z" R% J# s+ v; {
12345678848.000000 6 @9 U8 s5 F& ]$ O1 v( ]
; c* R+ `% x$ C
" g, K+ I \- u
而不是: 12345678900.000000 & m0 S! v7 v0 x7 \' P- }
- I; B+ F2 ^# |% P% l
# J8 P8 ?9 O7 E8 r% |! @5 a " a3 u/ e" e- f
分析:由于float在内存中占4个字节(32 bit),对于这个数来说会有数据丢失。实型数据是按照指数形式存储的。系统把一个实型数据分成小数部分和整数部分分别存放。在4个字节中,究竟用多少位来表示小数部分,多少位来表示指数部分,对于不同编译器可能会有不同。多数是以3 byte (24 bit) 表示小数部分(包括符号),以1 byte (8 bit) 表示指数部分(包括指数的符号)。 4 q; s2 `/ O$ b" m* h7 i
- {% S I3 V& r9 C
看看浮点数是怎样炼成的,呵呵: ( M) R$ P$ K" b) Y: {& ]+ w' o
3 T) s# V) _2 V1 \12345678900(十进制)=>
) e) G/ ~/ V! q/ T
% a. n: D- g8 B+ @1011011111110111000001110000110100(34位精确值) 5 X, U' _9 P4 _) a) W
( O$ F& m7 ^& W3 r9 e) G! p, I$ Q========>
# C" P0 w2 Q' U- F$ b; Y0 `! l
4 y/ V: D$ x I9 k8 c$ G符号位:0
' e* L4 ^% G; F: I' S +127
1 k! c% @1 q1 p8 n: U; @指数:33(100001=>00100001 =====> 10100000 5 Z' [: K6 B+ r g: }1 N
原码 阶码(移码)
1 b+ N3 t- i- e* |* I. x5 n& y2 F
- x" ~4 }: M+ y7 E: i尾数:1.01101111111011100000111(取24位)
5 P: @' C( @2 z- Z' ?( K1 A , J4 N4 j) y% b1 u9 O0 r
=>(注意:前面的1在实际存放时为了多存放一位而隐含,即浮点数的尾数的最高位永远隐含为1)
1 `, p/ a" `; `0 10100000 01101111111011100000111(实际放了尾数后面的23位)
; b8 X; P H2 K3 D0 " [ a 指数 尾数 0 S- O, w o' E; \
7 Y6 X7 X$ J4 S1 o) L7 a( h最后结果就是01010000001101111111011100000111
# ]- a: h2 o" s9 `/ A 1 x" E1 }; _5 C
现在再把它还原成整数:
/ Y. g. @; Q7 x& J+ w/ v P' C(1)取尾数23位:01101111111011100000111 : v% l& y, H* |& N; M, X) `- z
(2)在前面加上隐含的1,变成:101101111111011100000111
+ H: X: K, M' M/ i3 [: ^(3)取指数8位:10100000 2 [- ~6 G$ K0 i" C) p; b6 ]- }8 W
(4)指数减127得:100001(33)
! F- Z- m' A6 V# p6 ^(5)尾数向左移动10位(尾数本身23位,33-23=10):1011011111110111000001110000000000
6 g. i' s! K; g' b4 r此即12345678848 + H: V/ `) [' {! y* v
( c$ y) [: J3 Y$ a
) G3 q, d/ Z! b& _9 L& J' d" S! l' S
6 B* v$ Y. [7 N9 d; @启示:在需要精确答案的地方,要避免使用float和double;对于货币计算,要使用int、long(把零钱换成分来表示,这时就没有小数位,然后结果再除以相应的权值.)或BigDecimal。