深討Java中double在計算時精度丟失的問題

一昆婿、問題呈現(xiàn)

非常經(jīng)典問題生宛,其實不僅僅是 Java 語言灼芭,還是 JS 等語言的通病粉楚,即:
當(dāng)我們在計算 0.1+0.2 時馁痴,驚訝的發(fā)現(xiàn)黔漂,結(jié)果竟然不是 0.3订雾,而是:0.30000000000000004晴玖。

二晶默、問題分析

問題很簡單谨娜,是由于我們輸入的十進(jìn)制的 double 類型的數(shù)據(jù)在進(jìn)行計算的時候,計算機會先將其轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)磺陡,然后再進(jìn)行相關(guān)的運算趴梢。

然而在十進(jìn)制轉(zhuǎn)二進(jìn)制的過程中,有些十進(jìn)制數(shù)是無法使用一個有限的二進(jìn)制數(shù)來表達(dá)的币他,換言之就是轉(zhuǎn)換的時候出現(xiàn)了精度的丟失問題坞靶,所以導(dǎo)致最后在運算的過程中,自然就出現(xiàn)了我們看到的一幕蝴悉。

三彰阴、問題解決

Java 語言中最經(jīng)典的便是使用 BigDecimal 來解決。

整體思路是先將 double 類型的數(shù)據(jù)轉(zhuǎn)換成 BigDecimal 來進(jìn)行運算拍冠,最后再轉(zhuǎn)換成 double 類型的數(shù)據(jù)尿这。

但是此處有一個坑簇抵,即在將 double 轉(zhuǎn) BigDecimal 的時候,是可以有三種方式去實現(xiàn)的射众,其中兩種構(gòu)造方式碟摆,還有一種靜態(tài)方法方式:

// 方式一    
public BigDecimal(double val) {
    this(val,MathContext.UNLIMITED);
}

// 方式二
public BigDecimal(String val) {
    this(val.toCharArray(), 0, val.length());
}

// 方式三(其實底層就是方式二)
public static BigDecimal valueOf(double val) {
    // Reminder: a zero double returns '0.0', so we cannot fastpath
    // to use the constant ZERO.  This might be important enough to
    // justify a factory approach, a cache, or a few private
    // constants, later.
    return new BigDecimal(Double.toString(val));
}

從上面我們可以清晰地看出,其實 BigDecimal 是希望我們使用傳入 string 類型的數(shù)據(jù)的構(gòu)造方法责球。

所以解決起來就順?biāo)浦哿私孤模慈缦?demo:

double d1 = 0.1, d2 = 0.2;
System.out.println(d1 + d2);
System.out.println(new BigDecimal(d1).add(new BigDecimal(d2)).doubleValue());
System.out.println(BigDecimal.valueOf(d1).add(BigDecimal.valueOf(d2)).doubleValue());
System.out.println(new BigDecimal(Double.toString(d1)).add(new BigDecimal(Double.toString(d2))).doubleValue());

貼出運行的結(jié)果:

0.30000000000000004
0.30000000000000004
0.3
0.3

小 Tip:

很多人會說 public BigDecimal(double val) 這個構(gòu)造方法是 Java 的一個 bug,其實我并不認(rèn)同雏逾,我覺得是傳遞的 double 類型的參數(shù)的問題嘉裤,這個數(shù)據(jù)本身就存在精度的問題,所以導(dǎo)致了最終的計算問題栖博。

換言之屑宠,其實使用計算機的二進(jìn)制來表達(dá)十進(jìn)制的小數(shù),本身就是個偽命題仇让。

四典奉、double 轉(zhuǎn)二進(jìn)制

那么,為什么使用二進(jìn)制無法精確表達(dá)一個 double 類型的數(shù)據(jù)呢丧叽?下面來手動畫圖婆剖析下?lián)Q算的方法:

舉個栗子:15.75 -> 1111.11

  1. step1:拆分

將整數(shù)和小數(shù)部分拆分得:15 和 0.75

  1. step2:計算整數(shù)部分

整數(shù)部分是 15卫玖,計算得 1111,見下圖:

  1. step3:計算小數(shù)部分

小數(shù)部分是 0.75踊淳,計算得 0.11假瞬,見下圖:

  1. step4:合并

將整數(shù)部分和小數(shù)部分拼接得到最終的結(jié)果:1111.11

再舉個經(jīng)典的栗子:0.1 -> 0.000110011001100110011001100………..

還是四步走:

  1. step1:拆分

將整數(shù)部分和小數(shù)部分拆分得: 0 和 0.1

  1. step2:計算整數(shù)部分

整數(shù)部分是 0 ,計算得: 0

  1. step3:計算小數(shù)部分

小數(shù)部分是 0.1迂尝,計算得:0.0001100110011001100………….脱茉,計算過程見下圖:

  1. step4:合并

將整數(shù)部分和小數(shù)部分合并得到最終的結(jié)果:0.000110011001100110011001100………..

五、二進(jìn)制轉(zhuǎn) double

同樣舉個栗子:1111.11 -> 15.75

還是分四步走:

  1. step1:拆分

將整數(shù)和小數(shù)部分拆分得:1111 和 0.11

  1. step2:計算整數(shù)部分

整數(shù)部分 1111 計算得 15垄开,詳細(xì)計算過程見下圖:

  1. step3:計算小數(shù)部分

小數(shù)部分 0.11 計算得 0.75琴许,詳細(xì)計算過程見下圖:

  1. step4:合并

整數(shù)部分和小數(shù)部分合并得最終的結(jié)果:15.75

原文地址http://www.jetchen.cn/java-double-calculate/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市溉躲,隨后出現(xiàn)的幾起案子榜田,更是在濱河造成了極大的恐慌,老刑警劉巖锻梳,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件箭券,死亡現(xiàn)場離奇詭異,居然都是意外死亡唱蒸,警方通過查閱死者的電腦和手機邦鲫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門灸叼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來神汹,“玉大人庆捺,你說我怎么就攤上這事∑ㄎ海” “怎么了滔以?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氓拼。 經(jīng)常有香客問我你画,道長,這世上最難降的妖魔是什么桃漾? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任坏匪,我火速辦了婚禮,結(jié)果婚禮上撬统,老公的妹妹穿的比我還像新娘适滓。我一直安慰自己,他們只是感情好恋追,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布凭迹。 她就那樣靜靜地躺著,像睡著了一般苦囱。 火紅的嫁衣襯著肌膚如雪嗅绸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天撕彤,我揣著相機與錄音鱼鸠,去河邊找鬼。 笑死喉刘,一個胖子當(dāng)著我的面吹牛瞧柔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睦裳,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼造锅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了廉邑?” 一聲冷哼從身側(cè)響起哥蔚,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛛蒙,沒想到半個月后糙箍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡牵祟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年深夯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡咕晋,死狀恐怖雹拄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掌呜,我是刑警寧澤滓玖,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站质蕉,受9級特大地震影響势篡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜模暗,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一禁悠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兑宇,春花似錦绷蹲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至若厚,卻和暖如春拦英,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背测秸。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工疤估, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人霎冯。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓铃拇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沈撞。 傳聞我的和親對象是個殘疾皇子慷荔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容