Java double轉(zhuǎn)BigDecimal的注意事項(xiàng)說明
先上結(jié)論:
不要直接用double變量作為構(gòu)造BigDecimal的參數(shù)。
線上有這么一段Java代碼邏輯:
1,接口傳來一個(gè)JSON串,里面有個(gè)數(shù)字:57.3。
2,解析JSON并把這個(gè)數(shù)字保存在一個(gè)float變量。
3,把這個(gè)float變量賦值給一個(gè) BigDecimal對象,用的是BigDecimal的double參數(shù)的構(gòu)造:
new BigDecimal(double val)
4,把這個(gè)BigDecimal保存到MySQL數(shù)據(jù)庫,字段類型是decimal(15,2)。
這段代碼邏輯在線上跑了好久了,數(shù)據(jù)庫保存的值是57.3也沒什么問題,但是在今天debug的時(shí)候發(fā)現(xiàn),第三步的BigDecimal對象保存的值并不是57.3,而是57.299999237060546875,很明顯,出現(xiàn)了精度的問題。
至于數(shù)據(jù)庫最終保存了正確的57.3完全是因?yàn)樽侄晤愋驮O(shè)置為2位小數(shù),超過2位小數(shù)就四舍五入,所以才得到了正確的結(jié)果,相當(dāng)于MySQL給我們把這個(gè)精度問題掩蓋了。
總覺得這是個(gè)坑,所以研究了一下相關(guān)的知識。
首先是BigDecimal的double參數(shù)構(gòu)造,在官方JDK文檔中對這個(gè)構(gòu)造是這么描述的:
public BigDecimal(double val)
Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value. The scale of the returned BigDecimal is the smallest value such that (10scale × val) is an integer.
Notes:
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.
When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.
Parameters:
val - double value to be converted to BigDecimal.
Throws:
NumberFormatException - if val is infinite or NaN.
翻譯一下大概是這樣的:
1,BigDecimal(double val)構(gòu)造,用double當(dāng)參數(shù)來構(gòu)造一個(gè)BigDecimal對象。
2,但是這個(gè)構(gòu)造不太靠譜(unpredictable),你可能以為BigDecimal(0.1)就是妥妥的等于0.1,但是你以為你以為的就是你以為的?還真不是,BigDecimal(0.1)這貨實(shí)際上等于0.1000000000000000055511151231257827021181583404541015625,因?yàn)闇?zhǔn)確的來說0.1本身不能算是一個(gè)double(其實(shí)0.1不能代表任何一個(gè)定長二進(jìn)制分?jǐn)?shù))。
3,BigDecimal(String val)構(gòu)造是靠譜的,BigDecimal(“0.1”)就是妥妥的等于0.1,推薦大家用這個(gè)構(gòu)造。
4,如果你非得用一個(gè)double變量來構(gòu)造一個(gè)BigDecimal,沒問題,我們貼心的提供了靜態(tài)方法valueOf(double),這個(gè)方法跟new Decimal(Double.toString(double))效果是一樣的。
說白了就是別直接拿double變量做參數(shù),最好使用String類型做參數(shù)或者使用靜態(tài)方法valueOf(double),我寫了個(gè)例子試了一下:
public static void main(String[] args) { float a=57.3f; BigDecimal decimalA=new BigDecimal(a); System.out.println(decimalA); double b=57.3; BigDecimal decimalB=new BigDecimal(b); System.out.println(decimalB); double c=57.3; BigDecimal decimalC=new BigDecimal(Double.toString(c)); System.out.println(decimalC); double d=57.3; BigDecimal decimalD=BigDecimal.valueOf(d); System.out.println(decimalD); }
輸出結(jié)果:
57.299999237060546875 57.2999999999999971578290569595992565155029296875 57.3 57.3
以后還是盡量按照官方推薦的套路來,否則不知道什么時(shí)候又給自己挖坑了。
補(bǔ)充:double轉(zhuǎn)bigDecimal精度問題
float的精度 : 2^23 7位
double的精度: 2^52 16位
十進(jìn)制 轉(zhuǎn) 二進(jìn)制 存在精度差
double g= 12.35; BigDecimal bigG=new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); //期望得到12.4 System.out.println(“test G:”+bigG.doubleValue()); test G:12.3
原因:
定義double g= 12.35; 而在計(jì)算機(jī)中二進(jìn)制表示可能這是樣:定義了一個(gè)g=12.34444444444444449,
new BigDecimal(g) g還是12.34444444444444449 new BigDecimal(g).setScale(1, BigDecimal.ROUND_HALF_UP); 得到12.3
正確的定義方式是使用字符串構(gòu)造函數(shù):
new BigDecimal(“12.35”).setScale(1, BigDecimal.ROUND_HALF_UP)
首先得從計(jì)算機(jī)本身去討論這個(gè)問題。我們知道,計(jì)算機(jī)并不能識別除了二進(jìn)制數(shù)據(jù)以外的任何數(shù)據(jù)。無論我們使用何種編程語言,在何種編譯環(huán)境下工作,都要先 把源程序翻譯成二進(jìn)制的機(jī)器碼后才能被計(jì)算機(jī)識別。以上面提到的情況為例,我們源程序里的2.4是十進(jìn)制的,計(jì)算機(jī)不能直接識別,要先編譯成二進(jìn)制。但問 題來了,2.4的二進(jìn)制表示并非是精確的2.4,反而最為接近的二進(jìn)制表示是2.3999999999999999。原因在于浮點(diǎn)數(shù)由兩部分組成:指數(shù)和尾數(shù),這點(diǎn)如果知道怎樣進(jìn)行浮點(diǎn)數(shù)的二進(jìn)制與十進(jìn)制轉(zhuǎn)換,應(yīng)該是不難理解的。如果在這個(gè)轉(zhuǎn)換的過程中,浮點(diǎn)數(shù)參與了計(jì)算,那么轉(zhuǎn)換的過程就會(huì)變得不可預(yù) 知,并且變得不可逆。我們有理由相信,就是在這個(gè)過程中,發(fā)生了精度的丟失。而至于為什么有些浮點(diǎn)計(jì)算會(huì)得到準(zhǔn)確的結(jié)果,應(yīng)該也是碰巧那個(gè)計(jì)算的二進(jìn)制與 十進(jìn)制之間能夠準(zhǔn)確轉(zhuǎn)換。而當(dāng)輸出單個(gè)浮點(diǎn)型數(shù)據(jù)的時(shí)候,可以正確輸出,如
double d = 2.4; System.out.println(d);
輸出的是2.4,而不是2.3999999999999999。也就是說,不進(jìn)行浮點(diǎn)計(jì)算的時(shí)候,在十進(jìn)制里浮點(diǎn)數(shù)能正確顯示。這更印證了我以上的想法,即如果浮點(diǎn)數(shù)參與了計(jì)算,那么浮點(diǎn)數(shù)二進(jìn)制與十進(jìn)制間的轉(zhuǎn)換過程就會(huì)變得不可預(yù)知,并且變得不可逆。
事實(shí)上,浮點(diǎn)數(shù)并不適合用于精確計(jì)算,而適合進(jìn)行科學(xué)計(jì)算。這里有一個(gè)小知識:既然float和double型用來表示帶有小數(shù)點(diǎn)的數(shù),那為什么我們不稱 它們?yōu)椤靶?shù)”或者“實(shí)數(shù)”,要叫浮點(diǎn)數(shù)呢?因?yàn)檫@些數(shù)都以科學(xué)計(jì)數(shù)法的形式存儲(chǔ)。當(dāng)一個(gè)數(shù)如50.534,轉(zhuǎn)換成科學(xué)計(jì)數(shù)法的形式為5.053e1,它 的小數(shù)點(diǎn)移動(dòng)到了一個(gè)新的位置(即浮動(dòng)了)??梢姡↑c(diǎn)數(shù)本來就是用于科學(xué)計(jì)算的,用來進(jìn)行精確計(jì)算實(shí)在太不合適了。
在《Effective Java》這本書中也提到這個(gè)原則,float和double只能用來做科學(xué)計(jì)算或者是工程計(jì)算,在商業(yè)計(jì)算中我們要用java.math.BigDecimal。使用BigDecimal并且一定要用String來夠造。
BigDecimal用哪個(gè)構(gòu)造函數(shù)?
BigDecimal(double val) BigDecimal(String val)
上面的API簡要描述相當(dāng)?shù)拿鞔_,而且通常情況下,上面的那一個(gè)使用起來要方便一些。我們可能想都不想就用上了,會(huì)有什么問題呢?等到出了問題的時(shí)候,才發(fā)現(xiàn)參數(shù)是double的構(gòu)造方法的詳細(xì)說明中有這么一段:
Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.
原來我們?nèi)绻枰_計(jì)算,非要用String來夠造BigDecimal不可!
簡單來說 精確計(jì)算 ,需要用到bigDeicmal的String 構(gòu)造
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解
這篇文章主要介紹了Java 獲取本機(jī)的IP與MAC地址實(shí)現(xiàn)詳解的相關(guān)資料,需要的朋友可以參考下2016-09-09spring cloud Feign使用@RequestLine遇到的坑
這篇文章主要介紹了spring cloud Feign使用@RequestLine遇到的坑,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Netty分布式server啟動(dòng)流程N(yùn)io創(chuàng)建源碼分析
這篇文章主要介紹了Netty分布式server啟動(dòng)流程N(yùn)io創(chuàng)建源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03Java使用Kaptcha實(shí)現(xiàn)簡單的驗(yàn)證碼生成器
這篇文章主要為大家詳細(xì)介紹了Java如何使用Kaptcha實(shí)現(xiàn)簡單的驗(yàn)證碼生成器,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2024-02-02Java以struts2為例介紹如何實(shí)現(xiàn)圖片上傳
這篇文章主要介紹了Java struts2中如何實(shí)現(xiàn)圖片上傳的相關(guān)資料,需要的朋友可以參考下2015-11-11JFreeChart簡單實(shí)現(xiàn)光滑曲線繪制
這篇文章主要為大家詳細(xì)介紹了JFreeChart簡單實(shí)現(xiàn)光滑曲線的繪制,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06