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

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

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

概述

作為JAVA程序員,應(yīng)該或多或少跟BigDecimal打過交道。JAVA在java.math包中提供的API類BigDecimal,用來對超過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,對于整數(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é)果。

適用場景

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

常用方法

加減乘除

既然BigDecimal主要用于數(shù)值計(jì)算,那么最基礎(chǔ)的方法就是加減乘除。BigDecimal沒有對應(yīng)的數(shù)值類的基本數(shù)據(jù)類型,所以不能直接使用+-、*、/這樣的符號來進(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、subtractmultiply、divide方法都是有返回值的,返回值是一個(gè)新的BigDecimal對象,原來的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
    與四舍五入類似,這種是五舍六入。我們對于HALF_UP和HALF_DOWN可以理解成對于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對象

先看下面代碼

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這樣的冪,對應(yīng)我們在查看大數(shù)的時(shí)候,很多都是每3位會(huì)增加1個(gè)逗號。

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)對的方法是,在除法運(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ù)場景,例如會(huì)計(jì)金額計(jì)算。在可以忍受略微不精確的場景還是使用內(nèi)部提供的addsubtract、multiply、divide方法來進(jìn)行基礎(chǔ)的加減乘除運(yùn)算,運(yùn)算后會(huì)返回新的對象,原始的對象并不會(huì)改變。在使用BigDecimal的過程中,要注意創(chuàng)建對象、toString、比較數(shù)值、不能除盡時(shí)需要設(shè)置精度等問題。

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

相關(guān)文章

  • CorsFilter 過濾器解決跨域的處理

    CorsFilter 過濾器解決跨域的處理

    這篇文章主要介紹了CorsFilter 過濾器解決跨域的處理操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java中Map集合的常用方法(非常詳細(xì)!)

    Java中Map集合的常用方法(非常詳細(xì)!)

    Java中的Map是一種鍵值對存儲的數(shù)據(jù)結(jié)構(gòu),它提供了快速查找和訪問數(shù)據(jù)的能力,下面這篇文章主要給大家介紹了關(guān)于Java中Map集合的常用方法,需要的朋友可以參考下
    2024-01-01
  • Spring cloud oauth2如何搭建認(rèn)證資源中心

    Spring cloud oauth2如何搭建認(rèn)證資源中心

    這篇文章主要介紹了Spring cloud oauth2如何搭建認(rèn)證資源中心,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Mybatis中攔截器的使用場景和技巧分享

    Mybatis中攔截器的使用場景和技巧分享

    Mybatis提供了一些機(jī)制,可以允許我們在做數(shù)據(jù)庫操作的時(shí)候進(jìn)行我們額外的一些程序,當(dāng)然,這看起來并沒有JPA的EntityListener好用,本文小編將給大家詳細(xì)的介紹了Mybatis中攔截器的使用場景和技巧,需要的朋友可以參考下
    2023-10-10
  • Java實(shí)現(xiàn)深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)算法

    Java實(shí)現(xiàn)深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)算法

    深度優(yōu)先搜索(DFS)和廣度優(yōu)先搜索(BFS)是兩種基本的圖搜索算法,可用于圖的遍歷、路徑搜索等問題。DFS采用棧結(jié)構(gòu)實(shí)現(xiàn),從起點(diǎn)開始往深處遍歷,直到找到目標(biāo)節(jié)點(diǎn)或遍歷完整個(gè)圖;BFS采用隊(duì)列結(jié)構(gòu)實(shí)現(xiàn),從起點(diǎn)開始往廣處遍歷,直到找到目標(biāo)節(jié)點(diǎn)或遍歷完整個(gè)圖
    2023-04-04
  • Java中幾個(gè)Reference常見的作用詳解

    Java中幾個(gè)Reference常見的作用詳解

    這篇文章主要給大家介紹了Java中關(guān)于Reference多個(gè)作用的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。
    2017-06-06
  • springboot+dubbo啟動(dòng)項(xiàng)目時(shí)報(bào)錯(cuò) zookeeper not connected的問題及解決方案

    springboot+dubbo啟動(dòng)項(xiàng)目時(shí)報(bào)錯(cuò) zookeeper not connect

    這篇文章主要介紹了springboot+dubbo項(xiàng)目啟動(dòng)項(xiàng)目時(shí)報(bào)錯(cuò) zookeeper not connected的問題,本文給大家定位問題及解決方案,結(jié)合實(shí)例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • 用java的spring實(shí)現(xiàn)一個(gè)簡單的IOC容器示例代碼

    用java的spring實(shí)現(xiàn)一個(gè)簡單的IOC容器示例代碼

    本篇文章主要介紹了用java實(shí)現(xiàn)一個(gè)簡單的IOC容器示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-03-03
  • Java關(guān)于遠(yuǎn)程調(diào)試程序教程(以Eclipse為例)

    Java關(guān)于遠(yuǎn)程調(diào)試程序教程(以Eclipse為例)

    這篇文章主要介紹了Java關(guān)于遠(yuǎn)程調(diào)試程序教程(以Eclipse為例),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-06-06
  • 詳解Spring Security的formLogin登錄認(rèn)證模式

    詳解Spring Security的formLogin登錄認(rèn)證模式

    對于一個(gè)完整的應(yīng)用系統(tǒng),與登錄驗(yàn)證相關(guān)的頁面都是高度定制化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定制登錄頁面,也就是本文給大家介紹的formLogin模式登錄認(rèn)證模式,感興趣的朋友跟隨小編一起看看吧
    2019-11-11

最新評論