最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java BigDecimal精度丟失及常見問分析

 更新時(shí)間:2023年02月03日 11:39:21   作者:podongfeng  
這篇文章主要為大家介紹了java BigDecimal精度丟失及常見問分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

概述

作為JAVA程序員,應(yīng)該或多或少跟BigDecimal打過交道。JAVA在java.math包中提供的API類BigDecimal,用來對(duì)超過16位有效位的數(shù)進(jìn)行精確的運(yùn)算。

精度丟失

先從1個(gè)問題說起,看如下代碼

System.out.println(0.1 + 0.2);

最后打印出的結(jié)果是0.30000000000000004,而不是預(yù)期的0.3。

有經(jīng)驗(yàn)的開發(fā)同學(xué)應(yīng)該一下子看出來這就是因?yàn)閐ouble丟失精度導(dǎo)致。更深層次的原因,是因?yàn)槲覀兊挠?jì)算機(jī)底層是二進(jìn)制的,只有0和1,對(duì)于整數(shù)來說,從低到高的每1位代表了1、2、4、8、16...這樣的2的正次數(shù)冪,只要位數(shù)足夠,每個(gè)整數(shù)都可以分解成這樣的2的正次數(shù)冪組合,例如7D=111B,13D=1101B。但是到了小數(shù)這里,就會(huì)發(fā)現(xiàn)2的負(fù)次數(shù)冪值是0.5、0.25、0.125、0.0625這樣的值,但是并不是每個(gè)小數(shù)都可以分解成這樣的2的負(fù)次數(shù)冪組合,例如你無法精確湊出0.1。所以,double的0.1其實(shí)并不是精確的0.1,只是通過幾個(gè)2的負(fù)次數(shù)冪值湊的近似的0.1,所以會(huì)出現(xiàn)前面0.1 + 0.2 = 0.30000000000000004這樣的結(jié)果。

適用場(chǎng)景

雙精度浮點(diǎn)型變量double可以處理16位有效數(shù),但是某些場(chǎng)景下,即使已經(jīng)做到了16位有效位的數(shù)還是不夠,比如涉及金額計(jì)算,差一點(diǎn)就會(huì)導(dǎo)致賬目不平。

常用方法

加減乘除

既然BigDecimal主要用于數(shù)值計(jì)算,那么最基礎(chǔ)的方法就是加減乘除。BigDecimal沒有對(duì)應(yīng)的數(shù)值類的基本數(shù)據(jù)類型,所以不能直接使用+-、*、/這樣的符號(hào)來進(jìn)行計(jì)算,而要使用BigDecimal內(nèi)部的方法。

public BigDecimal add(BigDecimal augend)
public BigDecimal subtract(BigDecimal subtrahend)
public BigDecimal multiply(BigDecimal multiplicand)
public BigDecimal divide(BigDecimal divisor)

需要注意的是,BigDecimal是不可變的,所以,add、subtract、multiply、divide方法都是有返回值的,返回值是一個(gè)新的BigDecimal對(duì)象,原來的BigDecimal值并沒有變。

設(shè)置精度和舍入策略

可以通過setScale方法來設(shè)置精度和舍入策略。

public BigDecimal setScale(int newScale, RoundingMode roundingMode)

第1個(gè)參數(shù)newScale代表精度,即小數(shù)點(diǎn)后位數(shù);第2個(gè)參數(shù)roundingMode代表舍入策略,RoundingMode是一個(gè)枚舉,用來替代原來在BigDecimal定義的常量,原來在BigDecimal定義的常量已經(jīng)標(biāo)記為Deprecated。在RoundingMode類中也通過1個(gè)valueOf方法來給出映射關(guān)系

/**
 * Returns the {@code RoundingMode} object corresponding to a
 * legacy integer rounding mode constant in {@link BigDecimal}.
 *
 * @param  rm legacy integer rounding mode to convert
 * @return {@code RoundingMode} corresponding to the given integer.
 * @throws IllegalArgumentException integer is out of range
 */
public static RoundingMode valueOf(int rm) {
    return switch (rm) {
        case BigDecimal.ROUND_UP          -> UP;
        case BigDecimal.ROUND_DOWN        -> DOWN;
        case BigDecimal.ROUND_CEILING     -> CEILING;
        case BigDecimal.ROUND_FLOOR       -> FLOOR;
        case BigDecimal.ROUND_HALF_UP     -> HALF_UP;
        case BigDecimal.ROUND_HALF_DOWN   -> HALF_DOWN;
        case BigDecimal.ROUND_HALF_EVEN   -> HALF_EVEN;
        case BigDecimal.ROUND_UNNECESSARY -> UNNECESSARY;
        default -> throw new IllegalArgumentException("argument out of range");
    };
}

我們逐一看一下每個(gè)值的含義

  • UP
    直接進(jìn)位,例如下面代碼結(jié)果是3.15
BigDecimal pi = BigDecimal.valueOf(3.141);
System.out.println(pi.setScale(2, RoundingMode.UP));
  • DOWN
    直接舍去,例如下面代碼結(jié)果是3.1415
BigDecimal pi = BigDecimal.valueOf(3.14159);
System.out.println(pi.setScale(4, RoundingMode.DOWN));
  • CEILING
    如果是正數(shù),相當(dāng)于UP;如果是負(fù)數(shù),相當(dāng)于DOWN。
  • FLOOR
    如果是正數(shù),相當(dāng)于DOWN;如果是負(fù)數(shù),相當(dāng)于UP。
  • HALF_UP
    就是我們正常理解的四舍五入,實(shí)際上應(yīng)該也是最常用的。 下面的代碼結(jié)果是3.14
BigDecimal pi = BigDecimal.valueOf(3.14159);
System.out.println(pi.setScale(2, RoundingMode.HALF_UP));

下面的代碼結(jié)果是3.142

BigDecimal pi = BigDecimal.valueOf(3.14159);
System.out.println(pi.setScale(3, RoundingMode.HALF_UP));
  • HALF_DOWN
    與四舍五入類似,這種是五舍六入。我們對(duì)于HALF_UP和HALF_DOWN可以理解成對(duì)于5的處理不同,UP遇到5是進(jìn)位處理,DOWN遇到5是舍去處理,
  • HALF_EVEN
    如果舍棄部分左邊的數(shù)字為偶數(shù),相當(dāng)于HALF_DOWN;如果舍棄部分左邊的數(shù)字為奇數(shù),相當(dāng)于HALF_UP
  • UNNECESSARY
    非必要舍入。如果除去小數(shù)的后導(dǎo)0后,位數(shù)小于等于scale,那么就是去除scale位數(shù)后面的后導(dǎo)0;位數(shù)大于scale,拋出ArithmeticException。
    下面代碼結(jié)果是3.14
BigDecimal pi = BigDecimal.valueOf(3.1400);
System.out.println(pi.setScale(2, RoundingMode.UNNECESSARY));

下面代碼拋出ArithmeticException

BigDecimal pi = BigDecimal.valueOf(3.1400);
System.out.println(pi.setScale(1, RoundingMode.UNNECESSARY));

常見問題

創(chuàng)建BigDecimal對(duì)象

先看下面代碼

BigDecimal a = new BigDecimal(0.1);
System.out.println(a);

實(shí)際輸出的結(jié)果是0.1000000000000000055511151231257827021181583404541015625。其實(shí)這跟我們開篇引出的精度丟失是同一個(gè)問題,這里構(gòu)造方法中的參數(shù)0.1是double類型,本身無法精確表示0.1,雖然BigDecimal并不會(huì)導(dǎo)致精度丟失,但是在更加上游的源頭,double類型的0.1已經(jīng)丟失了精度,這里用一個(gè)已經(jīng)丟失精度的0.1來創(chuàng)建不會(huì)丟失精度的BigDecimal,精度還是會(huì)丟失。類似于使用2K的清晰度重新錄制了一遍原始只有360P的視頻,清晰度也不會(huì)優(yōu)于原始的360P。
所以,我們應(yīng)該盡量避免使用double來創(chuàng)建BigDecimal,確實(shí)源頭是double的,我們可以使用valueOf方法,這個(gè)方法會(huì)先調(diào)用Double.toString(val)來轉(zhuǎn)成String,這樣就不會(huì)產(chǎn)生精度丟失,下面的代碼結(jié)果就是0.1

BigDecimal a = BigDecimal.valueOf(0.1);
System.out.println(a);

順便說一下,BigDecimal還內(nèi)置了ZERO、ONE、TEN這樣的常量可以直接使用。

toString

這個(gè)問題比較隱蔽,在數(shù)據(jù)比較小的時(shí)候不會(huì)遇到,但是看如下代碼

BigDecimal a = BigDecimal.valueOf(987654321987654321.123456789123456789);
System.out.println(a);

最后實(shí)際輸出的結(jié)果是9.8765432198765427E+17。原因是System.out.println會(huì)自動(dòng)調(diào)用BigDecimal的toString方法,而這個(gè)方法會(huì)在必要時(shí)使用科學(xué)計(jì)數(shù)法,如果不想使用科學(xué)計(jì)數(shù)法,可以使用BigDecimal的toPlainString方法。另外提一下,BigDecimal還提供了一個(gè)toEngineeringString方法,這個(gè)方法也會(huì)使用科學(xué)技術(shù)法,不一樣的是,這里面的10都是3、6、9這樣的冪,對(duì)應(yīng)我們?cè)诓榭创髷?shù)的時(shí)候,很多都是每3位會(huì)增加1個(gè)逗號(hào)。

comparTo 和 equals

這個(gè)問題出現(xiàn)的不多,有經(jīng)驗(yàn)的開發(fā)同學(xué)在比較數(shù)值的時(shí)候,會(huì)自然而然使用comparTo方法。這里說一下BigDecimal的equals方法除了比較數(shù)值之外,還會(huì)比較scale精度,不同精度不會(huì)equles。
例如下面代碼分別會(huì)返回0false

BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.10");
System.out.println(a.compareTo(b));
System.out.println(a.equals(b));

不能除盡時(shí)ArithmeticException異常

上面提到的加減乘除的4個(gè)方法中,除法會(huì)比較特殊,因?yàn)榭赡艹霈F(xiàn)除不盡的情況,這時(shí)如果沒有設(shè)置精度,就會(huì)拋出ArithmeticException,因?yàn)檫@個(gè)是否能除盡是跟具體數(shù)值相關(guān)的,這會(huì)導(dǎo)致偶現(xiàn)的bug,更加難以排查。
例如下面代碼就會(huì)拋出ArithmeticException異常

BigDecimal a = new BigDecimal(1);
BigDecimal b = new BigDecimal(3);
System.out.println(a.divide(b));

應(yīng)對(duì)的方法是,在除法運(yùn)算時(shí),注意設(shè)置結(jié)果的精度和舍入模式,下面的代碼就能正常輸出結(jié)果0.33

BigDecimal a = new BigDecimal(1);
BigDecimal b = new BigDecimal(3);
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));

總結(jié)

BigDecimal主要用于double因?yàn)榫葋G失而不滿足的某些特殊業(yè)務(wù)場(chǎng)景,例如會(huì)計(jì)金額計(jì)算。在可以忍受略微不精確的場(chǎng)景還是使用內(nèi)部提供的add、subtract、multiply、divide方法來進(jìn)行基礎(chǔ)的加減乘除運(yùn)算,運(yùn)算后會(huì)返回新的對(duì)象,原始的對(duì)象并不會(huì)改變。在使用BigDecimal的過程中,要注意創(chuàng)建對(duì)象、toString、比較數(shù)值、不能除盡時(shí)需要設(shè)置精度等問題。

以上就是java BigDecimal精度丟失及常見問分析的詳細(xì)內(nèi)容,更多關(guān)于java BigDecimal精度丟失的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論

炉霍县| 泾阳县| 双流县| 博乐市| 阿合奇县| 桓仁| 禄丰县| 临沭县| 西城区| 柞水县| 宁化县| 乐至县| 读书| 清丰县| 沧州市| 南澳县| 黄大仙区| 五原县| 长沙市| 连平县| 江山市| 响水县| 井冈山市| 阳西县| 平远县| 吉木萨尔县| 凤冈县| 景宁| 延边| 勐海县| 本溪| 比如县| 无为县| 昆明市| 巧家县| 尖扎县| 文水县| 元氏县| 湘乡市| 东乌珠穆沁旗| 永德县|