欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java用BigDecimal類解決Double類型精度丟失的問(wèn)題

 更新時(shí)間:2020年12月29日 09:57:23   作者:天喬巴夏丶  
這篇文章主要介紹了Java用BigDecimal類解決Double類型精度丟失的問(wèn)題,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下

本篇要點(diǎn)

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

經(jīng)典問(wèn)題:浮點(diǎn)數(shù)精度丟失

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

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

這里簡(jiǎn)單討論一下十進(jìn)制數(shù)轉(zhuǎn)二進(jìn)制為什么會(huì)出現(xiàn)精度丟失的現(xiàn)象,十進(jìn)制數(shù)分為整數(shù)部分和小數(shù)部分,我們分開(kāi)來(lái)看看就知道原因?yàn)楹危?/p>

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

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

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

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

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

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

0.1 * 2 = 0.2 取整數(shù)部分0
0.2 * 2 = 0.4 取整數(shù)部分0
0.4 * 2 = 0.8 取整數(shù)部分0
0.8 * 2 = 1.6 取整數(shù)部分1
0.6 * 2 = 1.2 取整數(shù)部分1
0.2 * 2 = 0.4 取整數(shù)部分0 

//... 我想寫(xiě)到這就不必再寫(xiě)了,你應(yīng)該也已經(jīng)發(fā)現(xiàn),上面的過(guò)程已經(jīng)開(kāi)始循環(huán),小數(shù)部分永遠(yuǎn)不能為0

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

如何用BigDecimal解決double精度問(wèn)題?

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

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)操作。

既然這樣,那用BigDecimal就能夠很好解決這個(gè)問(wèn)題咯?

 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);
 }

你可以思考一下,控制臺(tái)輸出會(huì)是啥。

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

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

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

 // 傳入double
	public 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.
  // 可以看到實(shí)際上就是第二種
  return new BigDecimal(Double.toString(val));
 }

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

為了防止以后圖片可能會(huì)存在顯示問(wèn)題,這里再記錄一下:

new BigDecimal(double val)

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

new BigDecimal(String val)

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

BigDecimal.valueOf(double val)

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

Double的加減乘除運(yùn)算工具類

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

/**
 * 用于高精確處理常用的數(shù)學(xué)運(yùn)算
 */
public class ArithmeticUtils {
 //默認(rèn)除法運(yùn)算精度
 private static final int DEF_DIV_SCALE = 10;

 /**
  * 提供精確的加法運(yùn)算
  *
  * @param v1 被加數(shù)
  * @param v2 加數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供精確的加法運(yùn)算
  *
  * @param v1 被加數(shù)
  * @param v2 加數(shù)
  * @return 兩個(gè)參數(shù)的和
  */
 public static BigDecimal add(String v1, String v2) {
  BigDecimal b1 = new BigDecimal(v1);
  BigDecimal b2 = new BigDecimal(v2);
  return b1.add(b2);
 }

 /**
  * 提供精確的加法運(yùn)算
  *
  * @param v1 被加數(shù)
  * @param v2 加數(shù)
  * @param scale 保留scale 位小數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供精確的減法運(yùn)算
  *
  * @param v1 被減數(shù)
  * @param v2 減數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供精確的減法運(yùn)算。
  *
  * @param v1 被減數(shù)
  * @param v2 減數(shù)
  * @return 兩個(gè)參數(shù)的差
  */
 public static BigDecimal sub(String v1, String v2) {
  BigDecimal b1 = new BigDecimal(v1);
  BigDecimal b2 = new BigDecimal(v2);
  return b1.subtract(b2);
 }

 /**
  * 提供精確的減法運(yùn)算
  *
  * @param v1 被減數(shù)
  * @param v2 減數(shù)
  * @param scale 保留scale 位小數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供精確的乘法運(yùn)算
  *
  * @param v1 被乘數(shù)
  * @param v2 乘數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供精確的乘法運(yùn)算
  *
  * @param v1 被乘數(shù)
  * @param v2 乘數(shù)
  * @return 兩個(gè)參數(shù)的積
  */
 public static BigDecimal mul(String v1, String v2) {
  BigDecimal b1 = new BigDecimal(v1);
  BigDecimal b2 = new BigDecimal(v2);
  return b1.multiply(b2);
 }

 /**
  * 提供精確的乘法運(yùn)算
  *
  * @param v1 被乘數(shù)
  * @param v2 乘數(shù)
  * @param scale 保留scale 位小數(shù)
  * @return 兩個(gè)參數(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);
 }

 /**
  * 提供精確的乘法運(yùn)算
  *
  * @param v1 被乘數(shù)
  * @param v2 乘數(shù)
  * @param scale 保留scale 位小數(shù)
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供(相對(duì))精確的除法運(yùn)算,當(dāng)發(fā)生除不盡的情況時(shí),精確到
  * 小數(shù)點(diǎn)以后10位,以后的數(shù)字四舍五入
  *
  * @param v1 被除數(shù)
  * @param v2 除數(shù)
  * @return 兩個(gè)參數(shù)的商
  */

 public static double div(double v1, double v2) {
  return div(v1, v2, DEF_DIV_SCALE);
 }

 /**
  * 提供(相對(duì))精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時(shí),由scale參數(shù)指
  * 定精度,以后的數(shù)字四舍五入
  *
  * @param v1 被除數(shù)
  * @param v2 除數(shù)
  * @param scale 表示表示需要精確到小數(shù)點(diǎn)以后幾位。
  * @return 兩個(gè)參數(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();
 }

 /**
  * 提供(相對(duì))精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時(shí),由scale參數(shù)指
  * 定精度,以后的數(shù)字四舍五入
  *
  * @param v1 被除數(shù)
  * @param v2 除數(shù)
  * @param scale 表示需要精確到小數(shù)點(diǎn)以后幾位
  * @return 兩個(gè)參數(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ù)點(diǎn)后保留幾位
  * @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ù)點(diǎn)后保留幾位
  * @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ù)點(diǎn)后保留幾位
  * @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ù)點(diǎn)后保留幾位
  * @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);
 }

 /**
  * 比較大小
  * 阿里巴巴開(kāi)發(fā)規(guī)范明確:比較BigDecimal的等值需要使用compareTo,不可用equals
  * equals會(huì)比較值和精度,compareTo會(huì)忽略精度
  * @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開(kāi)發(fā)手冊(cè)關(guān)于BigDecimal的規(guī)定

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

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

關(guān)于這一點(diǎn),我們來(lái)看一個(gè)例子就明白了:

 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中對(duì)這兩個(gè)方法的解釋是這樣的:

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

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

相關(guān)文章

  • Java實(shí)現(xiàn)將Word轉(zhuǎn)換成Html的示例代碼

    Java實(shí)現(xiàn)將Word轉(zhuǎn)換成Html的示例代碼

    在業(yè)務(wù)中,常常會(huì)需要在瀏覽器中預(yù)覽Word文檔,或者需要將Word文檔轉(zhuǎn)成HTML文件保存,本文主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)Word轉(zhuǎn)換成Html的相關(guān)方法,希望對(duì)大家有所幫助
    2024-02-02
  • RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)

    RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)

    這篇文章主要為大家介紹了RocketMQ?源碼分析Broker消息刷盤(pán)服務(wù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-05-05
  • Java實(shí)現(xiàn)馬踏棋盤(pán)算法

    Java實(shí)現(xiàn)馬踏棋盤(pán)算法

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)馬踏棋盤(pán)算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 通過(guò)Java組合問(wèn)題看透回溯法

    通過(guò)Java組合問(wèn)題看透回溯法

    今天給大家分享一道LeetCode算法題,題目不是很困難,但是從這到簡(jiǎn)單的題目我們可以分析出回溯算法的幾個(gè)核心要點(diǎn),感興趣的可以了解一下
    2022-09-09
  • JVM虛擬機(jī)性能監(jiān)控與故障處理工具介紹

    JVM虛擬機(jī)性能監(jiān)控與故障處理工具介紹

    這篇文章主要為大家介紹了JVM虛擬機(jī)性能監(jiān)控與故障處理工具介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • 項(xiàng)目管理利器-Maven(Windows安裝)圖文教程

    項(xiàng)目管理利器-Maven(Windows安裝)圖文教程

    下面小編就為大家?guī)?lái)一篇項(xiàng)目管理利器-Maven(Windows安裝)圖文教程。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • java必學(xué)必會(huì)之方法的重載(overload)

    java必學(xué)必會(huì)之方法的重載(overload)

    java必學(xué)必會(huì)之方法的重載,介紹了方法的重載、構(gòu)造方法的重載,想要學(xué)好java方法的重載的朋友一定要好好閱讀這篇文章
    2015-12-12
  • Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹(shù)形結(jié)構(gòu)

    Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹(shù)形結(jié)構(gòu)

    在最近的開(kāi)發(fā)中,遇到了兩個(gè)類似的需求:都是基于 Stream 的父子樹(shù)形結(jié)構(gòu)操作,返回 List 集合對(duì)象給前端,下面給大家分享Java使用 Stream 流和 Lambda 組裝復(fù)雜父子樹(shù)形結(jié)構(gòu)的相關(guān)操作,感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • 詳解SpringBoot上傳圖片到阿里云的OSS對(duì)象存儲(chǔ)中

    詳解SpringBoot上傳圖片到阿里云的OSS對(duì)象存儲(chǔ)中

    這篇文章主要介紹了SpringBoot上傳圖片到阿里云的OSS對(duì)象存儲(chǔ)中,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-10-10
  • java實(shí)現(xiàn)HttpClient異步請(qǐng)求資源的方法

    java實(shí)現(xiàn)HttpClient異步請(qǐng)求資源的方法

    這篇文章主要介紹了java實(shí)現(xiàn)HttpClient異步請(qǐng)求資源的方法,實(shí)例分析了java基于http協(xié)議實(shí)現(xiàn)異步請(qǐng)求的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-07-07

最新評(píng)論