JAVA浮點(diǎn)數(shù)計(jì)算精度損失底層原理與解決方案
問(wèn)題:
對(duì)兩個(gè)double類型的值進(jìn)行運(yùn)算,有時(shí)會(huì)出現(xiàn)結(jié)果值異常的問(wèn)題。比如:
System.out.println(19.99+20); System.out.println(1.0-0.66); System.out.println(0.033*100); System.out.println(12.3/100);
輸出:
39.989999999999995 0.33999999999999997 3.3000000000000003 0.12300000000000001
Java中的簡(jiǎn)單浮點(diǎn)數(shù)類型float和double不能夠精確運(yùn)算。這個(gè)問(wèn)題其實(shí)不是JAVA的bug,因?yàn)橛?jì)算機(jī)本身是二進(jìn)制的,而浮點(diǎn)數(shù)實(shí)際上只是個(gè)近似值,所以從二進(jìn)制轉(zhuǎn)化為十進(jìn)制浮點(diǎn)數(shù)時(shí),精度容易丟失,導(dǎo)致精度下降。
關(guān)于精度損失的原理可以很簡(jiǎn)單的講,首先一個(gè)正整數(shù)在計(jì)算機(jī)中表示使用01010形式表示的,浮點(diǎn)數(shù)也不例外。
比如11,11除以2等于5余1
5除以2等于2余1
2除以2等于1余0
1除以2等于0余1
所以11二進(jìn)制表示為:1011.
double類型占8個(gè)字節(jié),64位,第1位為符號(hào)位,后面11位是指數(shù)部分,剩余部分是有效數(shù)字。
正整數(shù)除以2肯定會(huì)有個(gè)盡頭的,之后二進(jìn)制還原成十進(jìn)制只需要乘以2即可。
舉個(gè)例子:0.99用的有效數(shù)字部分,
0.99 * 2 = 1+0.98 --> 1
0.98 * 2 = 1+0.96 --> 1
0.96 * 2 = 1+0.92 -- >1
0.92 * 2 = 1+0.84 -- >1
...............
這樣周而復(fù)始是沒(méi)法有盡頭的,而double有效數(shù)字有限,所以必定會(huì)有損失,所以二進(jìn)制無(wú)法準(zhǔn)確表示0.99,就像十進(jìn)制無(wú)法準(zhǔn)確表示1/3一樣。
解決辦法:
在《Effective Java》中提到一個(gè)原則,那就是float和double只能用來(lái)作科學(xué)計(jì)算或者是工程計(jì)算,但在商業(yè)計(jì)算中我們要用java.math.BigDecimal,通過(guò)使用BigDecimal類可以解決上述問(wèn)題,首先需要注意的是,直接使用字符串來(lái)構(gòu)造BigDecimal是絕對(duì)沒(méi)有精度損失的,如果用double或者把double轉(zhuǎn)化成string來(lái)構(gòu)造BigDecimal依然會(huì)有精度損失,所以我覺(jué)得這種解決方法就是在使用中就把浮點(diǎn)數(shù)用string來(lái)表示存放,涉及到運(yùn)算直接用string構(gòu)造double,否則肯定會(huì)有精度損失。
1. 相加
/** * 相加 * @param double1 * @param double2 * @return */ public static double add(String doubleValA, String doubleValB) { BigDecimal a2 = new BigDecimal(doubleValA); BigDecimal b2 = new BigDecimal(doubleValB); return a2.add(b2).doubleValue(); }
2. 相減
/** * 相減 * @param double1 * @param double2 * @return */ public static double sub(String doubleValA, String doubleValB) { BigDecimal a2 = new BigDecimal(doubleValA); BigDecimal b2 = new BigDecimal(doubleValB); return a2.subtract(b2).doubleValue(); }
3. 相乘
/** * 相乘 * @param double1 * @param double2 * @return */ public static double mul(String doubleValA, String doubleValB) { BigDecimal a2 = new BigDecimal(doubleValA); BigDecimal b2 = new BigDecimal(doubleValB); return a2.multiply(b2).doubleValue(); }
4. 相除
/** * 相除 * @param double1 * @param double2 * @param scale 除不盡時(shí)指定精度 * @return */ public static double div(String doubleValA, String doubleValB, int scale) { BigDecimal a2 = new BigDecimal(doubleValA); BigDecimal b2 = new BigDecimal(doubleValB); return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); }
5. 主函數(shù)調(diào)用
public static void main(String[] args) { String doubleValA = "3.14159267"; String doubleValB = "2.358"; System.out.println("add:" + add(doubleValA, doubleValB)); System.out.println("sub:" + sub(doubleValA, doubleValB)); System.out.println("mul:" + mul(doubleValA, doubleValB)); System.out.println("div:" + div(doubleValA, doubleValB, 8)); }
結(jié)果展示如下所示:
add:5.49959267 sub:0.78359267 mul:7.40787551586 div:1.33231241
所以最好的方法是完全拋棄double,用string和java.math.BigDecimal。
java遵照IEEE制定的浮點(diǎn)數(shù)表示法來(lái)進(jìn)行float,double運(yùn)算。這種結(jié)構(gòu)是一種科學(xué)計(jì)數(shù)法,用符號(hào)、指數(shù)和尾數(shù)來(lái)表示,底數(shù)定為2——即把一個(gè)浮點(diǎn)數(shù)表示為尾數(shù)乘以2的指數(shù)次方再添上符號(hào)。具體底層如何存儲(chǔ)以及如何進(jìn)行運(yùn)行請(qǐng)繼續(xù)關(guān)注我的博客,后續(xù)我會(huì)將詳情總結(jié)好的。
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- Java LinkedHashMap 底層實(shí)現(xiàn)原理分析
- 在java中ArrayList集合底層的擴(kuò)容原理
- Java synchronize底層實(shí)現(xiàn)原理及優(yōu)化
- Java CAS底層實(shí)現(xiàn)原理實(shí)例詳解
- JAVA序列化和反序列化的底層實(shí)現(xiàn)原理解析
- JAVA字符串類型switch的底層原理詳析
- Java并發(fā)底層實(shí)現(xiàn)原理學(xué)習(xí)心得
- Java集合的總體框架相關(guān)知識(shí)總結(jié)
- Java集合中contains方法的效率對(duì)比分析
- Java基礎(chǔ)學(xué)習(xí)之集合底層原理
相關(guān)文章
Java 日期格式加上指定月數(shù)(一個(gè)期限)得到一個(gè)新日期的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java 日期格式加上指定月數(shù)(一個(gè)期限)得到一個(gè)新日期的實(shí)現(xiàn)代碼,需要的朋友可以參考下2018-05-05使用IDEA搭建Hadoop開(kāi)發(fā)環(huán)境的操作步驟(Window10為例)
經(jīng)過(guò)三次重裝,查閱無(wú)數(shù)資料后成功完成hadoop在win10上實(shí)現(xiàn)偽分布式集群,以及IDEA開(kāi)發(fā)環(huán)境的搭建。一步一步跟著本文操作可以避免無(wú)數(shù)天坑2021-07-07JavaWeb開(kāi)發(fā)使用Cookie創(chuàng)建-獲取-持久化、自動(dòng)登錄、購(gòu)物記錄、作用路徑
這篇文章主要介紹了JavaWeb開(kāi)發(fā)使用Cookie創(chuàng)建-獲取-持久化、自動(dòng)登錄、購(gòu)物記錄、作用路徑的相關(guān)知識(shí),非常不錯(cuò),對(duì)cookie持久化知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-08-08在spring?boot3中使用native?image的最新方法
這篇文章主要介紹了在spring?boot3中使用native?image?,今天我們用具體的例子來(lái)給大家演示一下如何正確的將spring boot3的應(yīng)用編譯成為native image,需要的朋友可以參考下2023-01-01解決SpringBoot jar包中的文件讀取問(wèn)題實(shí)現(xiàn)
這篇文章主要介紹了解決SpringBoot jar包中的文件讀取問(wèn)題實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08SpringMVC中Model和ModelAndView的EL表達(dá)式取值方法
下面小編就為大家分享一篇SpringMVC中Model和ModelAndView的EL表達(dá)式取值方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03詳解mybatis插入數(shù)據(jù)后返回自增主鍵ID的問(wèn)題
這篇文章主要介紹了mybatis插入數(shù)據(jù)后返回自增主鍵ID詳解,本文通過(guò)場(chǎng)景分析示例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-07-07