色综合图-色综合图片-色综合图片二区150p-色综合图区-玖玖国产精品视频-玖玖香蕉视频

您的位置:首頁技術(shù)文章
文章詳情頁

Java用BigDecimal類解決Double類型精度丟失的問題

瀏覽:24日期:2022-08-19 08:53:38
本篇要點

簡單描述浮點數(shù)十進(jìn)制轉(zhuǎn)二進(jìn)制精度丟失的原因。介紹幾種創(chuàng)建BigDecimal方式的區(qū)別。整理了高精度計算的工具類。學(xué)習(xí)了阿里巴巴Java開發(fā)手冊關(guān)于BigDecimal比較相等的規(guī)定。

經(jīng)典問題:浮點數(shù)精度丟失

精度丟失的問題是在其他計算機語言中也都會出現(xiàn),float和double類型的數(shù)據(jù)在執(zhí)行二進(jìn)制浮點運算的時候,并沒有提供完全精確的結(jié)果。產(chǎn)生誤差不在于數(shù)的大小,而是因為數(shù)的精度。

關(guān)于浮點數(shù)存儲精度丟失的問題,話題過于龐大,感興趣的同學(xué)可以自行搜索一下:【解惑】剖析float型的內(nèi)存存儲和精度丟失問題

這里簡單討論一下十進(jìn)制數(shù)轉(zhuǎn)二進(jìn)制為什么會出現(xiàn)精度丟失的現(xiàn)象,十進(jìn)制數(shù)分為整數(shù)部分和小數(shù)部分,我們分開來看看就知道原因為何:

十進(jìn)制整數(shù)如何轉(zhuǎn)化為二進(jìn)制整數(shù)?

將被除數(shù)每次都除以2,只要除到商為0就可以停止這個過程。

5 / 2 = 2 余 12 / 2 = 1 余 01 / 2 = 0 余 1 // 結(jié)果為 101

這個算法永遠(yuǎn)都不會無限循環(huán),整數(shù)永遠(yuǎn)都可以使用二進(jìn)制數(shù)精確表示,但小數(shù)呢?

十進(jìn)制小數(shù)如何轉(zhuǎn)化為二進(jìn)制數(shù)?

每次將小數(shù)部分乘2,取出整數(shù)部分,如果小數(shù)部分為0,就可以停止這個過程。

0.1 * 2 = 0.2 取整數(shù)部分00.2 * 2 = 0.4 取整數(shù)部分00.4 * 2 = 0.8 取整數(shù)部分00.8 * 2 = 1.6 取整數(shù)部分10.6 * 2 = 1.2 取整數(shù)部分10.2 * 2 = 0.4 取整數(shù)部分0 //... 我想寫到這就不必再寫了,你應(yīng)該也已經(jīng)發(fā)現(xiàn),上面的過程已經(jīng)開始循環(huán),小數(shù)部分永遠(yuǎn)不能為0

這個算法有一定概率會存在無限循環(huán),即無法用有限長度的二進(jìn)制數(shù)表示十進(jìn)制的小數(shù),這就是精度丟失問題產(chǎn)生的原因。

如何用BigDecimal解決double精度問題?

我們已經(jīng)明白為什么精度會存在丟失現(xiàn)象,那么我們就應(yīng)該知道,當(dāng)某個業(yè)務(wù)場景對double數(shù)據(jù)的精度要求非常高時,就必須采取某種手段來處理這個問題,這也是BigDecimal為什么會被廣泛應(yīng)用于金額支付場景中的原因啦。

BigDecimal類位于java.math包下,用于對超過16位有效位的數(shù)進(jìn)行精確的運算。一般來說,double類型的變量可以處理16位有效數(shù),但實際應(yīng)用中,如果超過16位,就需要BigDecimal類來操作。

既然這樣,那用BigDecimal就能夠很好解決這個問題咯?

public static void main(String[] args) {// 方法1 BigDecimal a = new BigDecimal(0.1); System.out.println('a --> ' + a);// 方法2 BigDecimal b = new BigDecimal('0.1'); System.out.println('b --> ' + b);// 方法3 BigDecimal c = BigDecimal.valueOf(0.1); System.out.println('c --> ' + c); }

你可以思考一下,控制臺輸出會是啥。

a --> 0.1000000000000000055511151231257827021181583404541015625b --> 0.1c --> 0.1

可以看到,使用方法一的構(gòu)造函數(shù)仍然出現(xiàn)了精度丟失的問題,而方法二和方法三符合我們的預(yù)期,為什么會這樣呢?

這三個方法其實對應(yīng)著三種不同的構(gòu)造函數(shù):

// 傳入doublepublic BigDecimal(double val) { this(val,MathContext.UNLIMITED); }// 傳入string 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)); }

關(guān)于這三個構(gòu)造函數(shù),JDK已經(jīng)給出了解釋,并用Notes標(biāo)注:

Java用BigDecimal類解決Double類型精度丟失的問題

為了防止以后圖片可能會存在顯示問題,這里再記錄一下:

new BigDecimal(double val)

該方法是不可預(yù)測的,以0.1為例,你以為你傳了一個double類型的0.1,最后會返回一個值為0.1的BigDecimal嗎?不會的,原因在于,0.1無法用有限長度的二進(jìn)制數(shù)表示,無法精確地表示為雙精度數(shù),最后的結(jié)果會是0.100000xxx。

new BigDecimal(String val)

該方法是完全可預(yù)測的,也就是說你傳入一個字符串'0.1',他就會給你返回一個值完全為0,1的BigDecimal,官方也表示,能用這個構(gòu)造函數(shù)就用這個構(gòu)造函數(shù)叭。

BigDecimal.valueOf(double val)

第二種構(gòu)造方式已經(jīng)足夠優(yōu)秀,可你還是想傳入一個double值,怎么辦呢?官方其實提供給你思路并且實現(xiàn)了它,可以使用Double.toString(double val)先將double值轉(zhuǎn)為String,再調(diào)用第二種構(gòu)造方式,你可以直接使用靜態(tài)方法:valueOf(double val)。

Double的加減乘除運算工具類

BigDecimal所創(chuàng)建的是對象,故我們不能使用傳統(tǒng)的+、-、*、/等算術(shù)運算符直接對其對象進(jìn)行數(shù)學(xué)運算,而必須調(diào)用其相對應(yīng)的方法。方法中的參數(shù)也必須是BigDecimal的對象。網(wǎng)上有很多這樣的工具類,這邊直接貼一下,邏輯不難,主要為了簡化項目中頻繁互相轉(zhuǎn)化的問題。

/** * 用于高精確處理常用的數(shù)學(xué)運算 */public class ArithmeticUtils { //默認(rèn)除法運算精度 private static final int DEF_DIV_SCALE = 10; /** * 提供精確的加法運算 * * @param v1 被加數(shù) * @param v2 加數(shù) * @return 兩個參數(shù)的和 */ public static double add(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2).doubleValue(); } /** * 提供精確的加法運算 * * @param v1 被加數(shù) * @param v2 加數(shù) * @return 兩個參數(shù)的和 */ public static BigDecimal add(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.add(b2); } /** * 提供精確的加法運算 * * @param v1 被加數(shù) * @param v2 加數(shù) * @param scale 保留scale 位小數(shù) * @return 兩個參數(shù)的和 */ public static String add(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精確的減法運算 * * @param v1 被減數(shù) * @param v2 減數(shù) * @return 兩個參數(shù)的差 */ public static double sub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2).doubleValue(); } /** * 提供精確的減法運算。 * * @param v1 被減數(shù) * @param v2 減數(shù) * @return 兩個參數(shù)的差 */ public static BigDecimal sub(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.subtract(b2); } /** * 提供精確的減法運算 * * @param v1 被減數(shù) * @param v2 減數(shù) * @param scale 保留scale 位小數(shù) * @return 兩個參數(shù)的差 */ public static String sub(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精確的乘法運算 * * @param v1 被乘數(shù) * @param v2 乘數(shù) * @return 兩個參數(shù)的積 */ public static double mul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2).doubleValue(); } /** * 提供精確的乘法運算 * * @param v1 被乘數(shù) * @param v2 乘數(shù) * @return 兩個參數(shù)的積 */ public static BigDecimal mul(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.multiply(b2); } /** * 提供精確的乘法運算 * * @param v1 被乘數(shù) * @param v2 乘數(shù) * @param scale 保留scale 位小數(shù) * @return 兩個參數(shù)的積 */ public static double mul(double v1, double v2, int scale) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return round(b1.multiply(b2).doubleValue(), scale); } /** * 提供精確的乘法運算 * * @param v1 被乘數(shù) * @param v2 乘數(shù) * @param scale 保留scale 位小數(shù) * @return 兩個參數(shù)的積 */ public static String mul(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供(相對)精確的除法運算,當(dāng)發(fā)生除不盡的情況時,精確到 * 小數(shù)點以后10位,以后的數(shù)字四舍五入 * * @param v1 被除數(shù) * @param v2 除數(shù) * @return 兩個參數(shù)的商 */ public static double div(double v1, double v2) { return div(v1, v2, DEF_DIV_SCALE); } /** * 提供(相對)精確的除法運算。當(dāng)發(fā)生除不盡的情況時,由scale參數(shù)指 * 定精度,以后的數(shù)字四舍五入 * * @param v1 被除數(shù) * @param v2 除數(shù) * @param scale 表示表示需要精確到小數(shù)點以后幾位。 * @return 兩個參數(shù)的商 */ public static double div(double v1, double v2, int scale) { if (scale < 0) { throw new IllegalArgumentException('The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 提供(相對)精確的除法運算。當(dāng)發(fā)生除不盡的情況時,由scale參數(shù)指 * 定精度,以后的數(shù)字四舍五入 * * @param v1 被除數(shù) * @param v2 除數(shù) * @param scale 表示需要精確到小數(shù)點以后幾位 * @return 兩個參數(shù)的商 */ public static String div(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException('The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v1); return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 提供精確的小數(shù)位四舍五入處理 * * @param v 需要四舍五入的數(shù)字 * @param scale 小數(shù)點后保留幾位 * @return 四舍五入后的結(jié)果 */ public static double round(double v, int scale) { if (scale < 0) { throw new IllegalArgumentException('The scale must be a positive integer or zero'); } BigDecimal b = new BigDecimal(Double.toString(v)); return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue(); } /** * 提供精確的小數(shù)位四舍五入處理 * * @param v 需要四舍五入的數(shù)字 * @param scale 小數(shù)點后保留幾位 * @return 四舍五入后的結(jié)果 */ public static String round(String v, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } BigDecimal b = new BigDecimal(v); return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 取余數(shù) * * @param v1 被除數(shù) * @param v2 除數(shù) * @param scale 小數(shù)點后保留幾位 * @return 余數(shù) */ public static String remainder(String v1, String v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); } /** * 取余數(shù) BigDecimal * * @param v1 被除數(shù) * @param v2 除數(shù) * @param scale 小數(shù)點后保留幾位 * @return 余數(shù) */ public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( 'The scale must be a positive integer or zero'); } return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP); } /** * 比較大小 * 阿里巴巴開發(fā)規(guī)范明確:比較BigDecimal的等值需要使用compareTo,不可用equals * equals會比較值和精度,compareTo會忽略精度 * @param v1 被比較數(shù) * @param v2 比較數(shù) * @return 如果v1 大于v2 則 返回true 否則false */ public static boolean compare(String v1, String v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); int bj = b1.compareTo(b2); boolean res; if (bj > 0) res = true; else res = false; return res; }}阿里巴巴Java開發(fā)手冊關(guān)于BigDecimal的規(guī)定

【強制】如上所示BigDecimal的等值比較應(yīng)使用compareTo()方法,而不是equals()方法。

說明:equals()方法會比較值和精度(1.0和1.00返回結(jié)果為false),而compareTo()則會忽略精度。

關(guān)于這一點,我們來看一個例子就明白了:

public static void main(String[] args) { BigDecimal a = new BigDecimal('1'); BigDecimal b = new BigDecimal('1.0'); System.out.println(a.equals(b)); // false System.out.println(a.compareTo(b)); //0 表示相等 }

JDK中對這兩個方法的解釋是這樣的:

使用compareTo方法,兩個值相等但是精度不同的BigDecimal對象會被認(rèn)為是相等的,比如2.0和2.00。建議使用x.compareTo(y) <op> 0來表示(<, == , > , >= , != , <=)中的其中一個關(guān)系,<op>就表示運算符。 equals方法與compareTo方法不同,此方法僅在兩個BigDecimal對象的值和精度都相等時才被認(rèn)為是相等的,如2.0和2.00就是不相等的。

以上就是Java用BigDecimal類解決Double類型精度丟失的問題的詳細(xì)內(nèi)容,更多關(guān)于Java BigDecimal解決Double類型精度丟失的資料請關(guān)注好吧啦網(wǎng)其它相關(guān)文章!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 精品一区二区三区中文字幕 | 长腿校花被啪到腿软视频 | 欧美亚洲另类视频 | 国产精品欧美亚洲韩国日本不卡 | a级高清观看视频在线看 | 欧美a级毛片 | 免费视频观看在线www日本 | 大片国产片日本观看免费视频 | 日鲁夜鲁鲁狠狠综合视频 | 中文国产成人精品久久水 | 91欧美视频 | 99精品视频免费观看 | 美国全免费特一级毛片 | 久草在现视频 | 日韩在线一区二区三区免费视频 | 日本乱人伦片中文字幕三区 | 草草草在线 | 欧美另类自拍 | a黄视频 | 久久99国产精品视频 | 日韩国产片 | 岛国搬运工最新网地址 | 一区二区三区四区在线 | 狠狠久久综合 | 亚洲区精选网址 | 草草视频手机在线观看视频 | 欧美一级毛片免费高清的 | 亚洲丝袜另类 | 久久99爱视频 | 国产香蕉98碰碰久久人人 | 美女黄色免费在线观看 | 九九九九视频 | 99久99久6久热在线播放 | 东京一区二区三区高清视频 | 亚洲在线观看免费 | 欧美一级毛片免费高清的 | 亚洲 欧美 日韩在线 | 亚洲精品一区二区中文 | 国产精品久久久久久久久岛 | 一区二区欧美视频 | 91精品手机国产露脸 |