Java中double精度丟失問(wèn)題原因及解決辦法
double類型精度丟失問(wèn)題:
0.1*0.1使用計(jì)算器計(jì)算是0.01,代碼里卻是0.010000000000000002
public class HelloWorld { public static void main(String []args) { double number1 = 0.1; double number2 = 0.1; double result = number1 * number2 ; System.out.println("使用double運(yùn)算結(jié)果: "+result); } }
為什么會(huì)這樣呢?這就是精度丟失問(wèn)題造成的。
為什么會(huì)出現(xiàn)精度丟失?
因?yàn)橛?jì)算機(jī)只能識(shí)別0和1,即二進(jìn)制,無(wú)論哪種編程語(yǔ)言,都需要翻譯成二進(jìn)制才能被計(jì)算機(jī)識(shí)別。
很多人還知道這樣一句話:這種舍入誤差的主要原因是浮點(diǎn)數(shù)值采用二進(jìn)制系統(tǒng)表示, 而在二進(jìn)制系統(tǒng)中無(wú)法精確地表示分?jǐn)?shù) 1/10。這就好像十進(jìn)制無(wú)法精確地表示分?jǐn)?shù) 1/3—樣。
針對(duì)十進(jìn)制,1除以3是除不盡的。很好理解,因?yàn)槲覀円恢苯佑|的就是十進(jìn)制,等于0.333333… 很好理解
但是:二進(jìn)制系統(tǒng)中無(wú)法精確地表示分?jǐn)?shù) 1/10。為啥呢。就有點(diǎn)不理解了
《Java核心技術(shù)卷》書上也是這么寫的。
十進(jìn)制 轉(zhuǎn)二進(jìn)制(每次將小數(shù)部分乘2,取出整數(shù)部分,如果小數(shù)部分為0,就可以停止這個(gè)過(guò)程):十進(jìn)制0.1
0.1*2=0.2 0.2*2=0.4 0.4*2=0.8 0.8*2=1.6 0.6*2=1.2 0.2*2=0.4 0.4*2=0.8 //... 應(yīng)該已經(jīng)發(fā)現(xiàn),上面的過(guò)程已經(jīng)開(kāi)始循環(huán),小數(shù)部分永遠(yuǎn)不能為0
怎么解決精度丟失問(wèn)題?
在商城里面計(jì)算訂單金額的時(shí)候,我們就不得不解決這個(gè)問(wèn)題了,這時(shí)候就用到了BigDecimal
BigDecimal類位于java.math包下,用于對(duì)超過(guò)16位有效位的數(shù)進(jìn)行精確的運(yùn)算。
一般來(lái)說(shuō),double類型的變量可以處理16位有效數(shù),
但實(shí)際應(yīng)用中,如果超過(guò)16位,就需要BigDecimal類來(lái)操作
new BigDecimal(double val)
new BigDecimal(String val)
BigDecimal.valueOf(double val)
將double轉(zhuǎn)為BigDecimal的時(shí)候,需要先把double轉(zhuǎn)換為字符串,然后再作為BigDecimal(String val)構(gòu)造函數(shù)的參數(shù),這樣才能避免出現(xiàn)精度問(wèn)題。
import java.math.BigDecimal; public class BigDecimalUtil { /** * double類型的加法運(yùn)算(不需要舍入) * @param d1 * @param d2 * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double addDouble(double d1, double d2) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.add(p2).doubleValue(); } /** * double類型的加法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double addDouble(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double類型的超大數(shù)值加法運(yùn)算(超過(guò)50 0000)(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 返回字符串,不然double數(shù)字會(huì)轉(zhuǎn)成科學(xué)計(jì)數(shù)法顯示 */ public static String addDoubleToStr(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.add(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double類型的減法運(yùn)算 * @param d1 * @param d2 * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double subtractDouble(double d1, double d2) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.subtract(p2).doubleValue(); } /** * double類型的減法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double subtractDouble(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double類型的超大數(shù)值減法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 返回字符串,不然double數(shù)字會(huì)轉(zhuǎn)成科學(xué)計(jì)數(shù)法顯示 */ public static String subtractDoubleToStr(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.subtract(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double類型的乘法運(yùn)算 * * @param d1 * @param d2 * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double multiplyDouble(double d1, double d2) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.multiply(p2).doubleValue(); } /** * double類型的乘法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double multiplyDouble(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double類型的超大數(shù)值的乘法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 返回字符串,不然double數(shù)字會(huì)轉(zhuǎn)成科學(xué)計(jì)數(shù)法顯示 */ public static String multiplyDoubleToStr(double d1, double d2, int scale) { BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.multiply(p2).setScale(scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } /** * double類型的除法運(yùn)算(需要舍入) * * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 不加doubleValue()則, 返回BigDecimal對(duì)象 */ public static double divideDouble(double d1, double d2, int scale) { if (scale < 0) { throw new IllegalArgumentException("Parameter error"); } BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * double類型的超大數(shù)值的除法運(yùn)算(需要舍入) * @param d1 * @param d2 * @param scale 保留scale位小數(shù) * @return 返回字符串,不然double數(shù)字會(huì)轉(zhuǎn)成科學(xué)計(jì)數(shù)法顯示 */ public static String divideDoubleToStr(double d1, double d2, int scale) { if (scale < 0) { throw new IllegalArgumentException("Parameter error"); } BigDecimal p1 = new BigDecimal(Double.toString(d1)); BigDecimal p2 = new BigDecimal(Double.toString(d2)); return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).toPlainString(); } }
各個(gè)roundingMode詳解如下:
- ROUND_UP:非0時(shí),舍棄小數(shù)后(整數(shù)部分)加1,比如12.49結(jié)果為13,-12.49結(jié)果為 -13
- ROUND_DOWN:直接舍棄小數(shù)
- ROUND_CEILING:如果 BigDecimal 是正的,則做 ROUND_UP 操作;如果為負(fù),則做 ROUND_DOWN 操作 (一句話:取附近較大的整數(shù))
- ROUND_FLOOR: 如果 BigDecimal 是正的,則做 ROUND_DOWN 操作;如果為負(fù),則做 ROUND_UP 操作(一句話:取附近較小的整數(shù))
- ROUND_HALF_UP:四舍五入(取更近的整數(shù))
- ROUND_HALF_DOWN:跟ROUND_HALF_UP 差別僅在于0.5時(shí)會(huì)向下取整
- ROUND_HALF_EVEN:取最近的偶數(shù)
- ROUND_UNNECESSARY:不需要取整,如果存在小數(shù)位,就拋ArithmeticException 異常
總結(jié)
到此這篇關(guān)于Java中double精度丟失問(wèn)題原因及解決辦法的文章就介紹到這了,更多相關(guān)Java double精度丟失問(wèn)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java使用kafka發(fā)送和生產(chǎn)消息的示例
本篇文章主要介紹了Java使用kafka發(fā)送和生產(chǎn)消息的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04Spring使用@Autowired為抽象父類注入依賴代碼實(shí)例
這篇文章主要介紹了Spring使用@Autowired為抽象父類注入依賴代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11IDEA新建javaWeb以及Servlet簡(jiǎn)單實(shí)現(xiàn)小結(jié)
這篇文章主要介紹了IDEA新建javaWeb以及Servlet簡(jiǎn)單實(shí)現(xiàn)小結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11SpringBoot 導(dǎo)出數(shù)據(jù)生成excel文件返回方式
這篇文章主要介紹了SpringBoot 導(dǎo)出數(shù)據(jù)生成excel文件返回方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10SpringBoot優(yōu)化接口響應(yīng)時(shí)間的九個(gè)技巧
在實(shí)際開(kāi)發(fā)中,提升接口響應(yīng)速度是一件挺重要的事,特別是在面臨大量用戶請(qǐng)求的時(shí)候,本文為大家整理了9個(gè)SpringBoot優(yōu)化接口響應(yīng)時(shí)間的技巧,希望對(duì)大家有所幫助2024-01-01Java?數(shù)據(jù)結(jié)構(gòu)進(jìn)階二叉樹(shù)題集下
二叉樹(shù)可以簡(jiǎn)單理解為對(duì)于一個(gè)節(jié)點(diǎn)來(lái)說(shuō),最多擁有一個(gè)上級(jí)節(jié)點(diǎn),同時(shí)最多具備左右兩個(gè)下級(jí)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。本文將帶你通過(guò)實(shí)際題目來(lái)熟練掌握2022-04-04