原文鏈接
前幾天有個(gè)同學(xué)在問猜嘱,float數(shù)運(yùn)算的時(shí)候精度出現(xiàn)問題衅枫,比如
public class Main {
public static void main(String[] args) {
int a = 3;
float b = 0.9f;
System.out.print(a * b);
}
}
按道理來說結(jié)果應(yīng)該是2.7
,但是運(yùn)行的結(jié)果卻不是。
定點(diǎn)數(shù) 和 浮點(diǎn)數(shù)
記得老師上課的時(shí)候講的朗伶,“你們記住弦撩,定點(diǎn)數(shù)就是整數(shù),浮點(diǎn)數(shù)就是下小數(shù)”腕让,當(dāng)時(shí)覺得好像沒有問題孤钦。
在計(jì)算機(jī)中,只有定點(diǎn)數(shù)和浮點(diǎn)數(shù)纯丸,沒有整數(shù)和小數(shù)偏形,浮點(diǎn)的“點(diǎn)”是小數(shù)點(diǎn)。 把小數(shù)點(diǎn)固定觉鼻,通常固定在最右面俊扭,就是定點(diǎn)數(shù)。 把小數(shù)點(diǎn)浮動坠陈,就是浮點(diǎn)數(shù)萨惑。
進(jìn)制轉(zhuǎn)換
計(jì)算機(jī)存儲任何數(shù)字都是基于二進(jìn)制,那么浮點(diǎn)數(shù)怎么存儲成二進(jìn)制仇矾?定點(diǎn)庸蔼、浮點(diǎn),“點(diǎn)”是什么意思贮匕?
這個(gè)在 IEEE754 浮點(diǎn)數(shù)標(biāo)準(zhǔn)里面定義的,Java遵循了這個(gè)標(biāo)準(zhǔn)姐仅。 過程如下:
-
第一步:轉(zhuǎn)換成二進(jìn)制
十進(jìn)制數(shù)字轉(zhuǎn)化成二進(jìn)制表示形式,通過將整數(shù)部分除2取余、小數(shù)部分乘2取整來完成轉(zhuǎn)換掏膏,所以這里有可能丟失精度0.12 =>0.00011110101110000101000111101...
第二步:用二進(jìn)制科學(xué)計(jì)算法表示
將小數(shù)點(diǎn)移動到第一個(gè)1的右邊
0.00011110101110000101000111101... =>
1.1110101110000101000111101... * 2 ^ -4
- 第三步:表示成 IEEE 754 形式
這里也可能會丟失精度
1 8位 23位 存不下了
0 01111011 11101011100001010001111 (01...)
+ 2的-4次冪 除去整數(shù)部分1之后剩余的尾數(shù) 0舍1入后忽略這部分劳翰,精度就這樣沒了
所以最后我們需要轉(zhuǎn)換成十進(jìn)制的時(shí)候,丟失部分的進(jìn)度就沒辦法回來了
哪些數(shù)能精確表示馒疹?
舉個(gè)例子:0.1 在計(jì)算機(jī)中可以精確表示嗎佳簸?
我們試一下,試著乘2取整把他轉(zhuǎn)換成二進(jìn)制颖变。
(1) 0.1 x 2 = 0.2 取整數(shù)位 0 得 0.0
(2) 0.2 x 2 = 0.4 取整數(shù)位 0 得 0.00
(3) 0.4 x 2 = 0.8 取整數(shù)位 0 得 0.000
(4) 0.8 x 2 = 1.6 取整數(shù)位 1 得 0.0001
(5) 0.6 x 2 = 0.2 取整數(shù)位 1 得 0.00011
(6) 0.2 x 2 = 0.4 取整數(shù)位 0 得 0.000110
(7) 0.4 x 2 = 0.8 取整數(shù)位 0 得 0.0001100
(8) 0.8 x 2 = 1.6 取整數(shù)位 1 得 0.00011001
(9) 0.6 x 2 = 1.2 取整數(shù)位 1 得 0.000110011
(10) 0.2 x 2 = 0.4 取整數(shù)位 1 得 0.0001100110
(n)...
啊哈生均,返現(xiàn)了沒有,居然在循環(huán)~悼做,所以我們沒辦法求出0.1用二進(jìn)制表示的準(zhǔn)確值疯特,所以在第一步就已經(jīng)丟失了精度
其實(shí)在0.1 ~ 0.9 中,只有0.5能用二進(jìn)制精確表示:
(1) 0.5 x 2 = 1.0 取整數(shù)位 1 得 0.1
(2) 0.0 x 2 = 0.0 取整數(shù)位 0 得 0.10
哈哈肛走,后面繼續(xù)下去就都是0了漓雅,所以0.5的精度并沒有發(fā)生丟失。
所以我們由此可以推出一個(gè)條件
如果一個(gè)十進(jìn)制數(shù)的最后一位是5,那么它可以用二進(jìn)制精確表示朽色。
解決方案
一種思路
針對小數(shù)精度不夠的問題(例如 0.1)邻吞,軟件可以人為的在數(shù)據(jù)最后一位補(bǔ) 5, 也就是 0.15葫男,這樣犧牲一位抱冷,但是可以保證數(shù)據(jù)精度,還原再把那個(gè)尾巴 5 去掉梢褐。
另一種辦法
講道理旺遮,上一種方法我們實(shí)際開發(fā)的時(shí)候采用是不現(xiàn)實(shí)的,float
用 4 個(gè)字節(jié)存儲盈咳,double
用 8 個(gè)字節(jié)存儲耿眉,精度肯定是有限的,但是一般的計(jì)算都足夠了鱼响。
需要高精度計(jì)算的時(shí)候鸣剪,我們可以采用BigDecimal
類來計(jì)算。但是速度會比float
和double
慢很多~丈积。