Android(Java)計(jì)算精度校正


現(xiàn)象

float total = 1.0f;
float f1 = .1f;
float f2 = .2f;
float f3 = .3f;
float f4 = total - f1 - f2 - f3;

得到的f4值是多少?
稍有經(jīng)驗(yàn)的程序員估計(jì)都會(huì)想「肯定不是0.4」
Bingo!答案是0.39999998


原理

Java在存儲(chǔ)float和double數(shù)據(jù)時(shí),整數(shù)和小數(shù)部分分別轉(zhuǎn)為二進(jìn)制表示
例如2.5,在內(nèi)存中32位的存儲(chǔ)結(jié)果為

1100 0000 0010 0000 0000 0000 0000 0000

沒有精度丟失,因?yàn)?.5剛好可以轉(zhuǎn)為二級(jí)制小數(shù)的1
但如果是2.6呢?小數(shù)部分會(huì)表示為

1001 1001 1001 1001 1001 1001 1001 1001

1001的無限循環(huán),但存儲(chǔ)位數(shù)是有限的,超出部分不得不被舍棄掉,就造成了精度丟失


跳坑

Java提供了BigDecimal類處理類似問題,這也是很多博客推薦的解法
話不多說,直接上代碼

float total = 1.0f;
float f1 = .1f;
float f2 = .2f;
float f3 = .3f;
BigDecimal totalB = new BigDecimal(total);
BigDecimal f1B = new BigDecimal(f1);
BigDecimal f2B = new BigDecimal(f2);
BigDecimal f3B = new BigDecimal(f3);
float f4B = totalB.subtract(f1B).subtract(f2B).subtract(f3B).floatValue();

得到的f4B值是多少?
多數(shù)人會(huì)想「肯定0.4了」
答案還是0.39999998


分析

BigDecimal只有傳參為double類型的構(gòu)造方法,所以這里雖然傳入的是float,但調(diào)用的是:

public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}

跟進(jìn)去看看是怎么處理傳參的:

public BigDecimal(double val, MathContext mc) {
  if (Double.isInfinite(val) || Double.isNaN(val))
    throw new NumberFormatException("Infinite or NaN");
  // Translate the double into sign, exponent and significand, according
  // to the formulae in JLS, Section 20.10.22.
  long valBits = Double.doubleToLongBits(val);
  int sign = ((valBits >> 63) == 0 ? 1 : -1);
  int exponent = (int) ((valBits >> 52) & 0x7ffL);
  long significand = (exponent == 0
    ? (valBits & ((1L << 52) - 1)) << 1
    : (valBits & ((1L << 52) - 1)) | (1L << 52));
  exponent -= 1075;
  ...
}

double本身就無法無損表示


解決

一個(gè)小改動(dòng),就可以解決問題:

float total = 1.0f;
float f1 = .1f;
float f2 = .2f;
float f3 = .3f;
BigDecimal totalB = new BigDecimal(String.valueOf(total));
BigDecimal f1B = new BigDecimal(String.valueOf(f1));
BigDecimal f2B = new BigDecimal(String.valueOf(f2));
BigDecimal f3B = new BigDecimal(String.valueOf(f3));
float f4B = totalB.subtract(f1B).subtract(f2B).subtract(f3B).floatValue();

為什么傳參為String類型的構(gòu)造方法可以保留精度?
答案都在源碼里

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容