真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

BigDecimal的介紹和使用

對于Java開發(fā)人員來說,只要日常工作中涉及到算術(shù)運算,那必然會跟BigDecimal這個類打交道。也許我們可以記住一些使用的注意事項,如使用String的構(gòu)造函數(shù)而不是double的構(gòu)造函數(shù)來避免精度問題。但是對于一個5000行的龐然大物,僅僅了解兩個構(gòu)造函數(shù)還不足以支撐我們大規(guī)模應(yīng)用的信念,好在源代碼對我們是完全開放的,那不妨來一次源代碼的親密接觸。

站在用戶的角度思考問題,與客戶深入溝通,找到裕華網(wǎng)站設(shè)計與裕華網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:做網(wǎng)站、成都網(wǎng)站設(shè)計、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名注冊、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務(wù)覆蓋裕華地區(qū)。

 

按照J(rèn)ava的慣例在每個重要類前面都有一篇論文式的注釋,一般情況下把這段理解了應(yīng)付個面試是沒啥問題的。BigDecimal也不例外的在類注釋上花了近200行,我們做個簡單的摘要:

2 首先給出BigDecimal的定義為任意精度的有符號十進制數(shù)。BigDecimal可以表示為一個任意精度的無刻度值和一個32位整型的刻度。

2 BigDecimal提供了一系列的方法,如算術(shù)操作、標(biāo)度控制、舍入、比較等等方法,總之很強大。

2 BigDecimal通過precision、scale、rounding mode和MathContext類來控制標(biāo)度和進行舍入操作。

2 BigDecimal的equals方法并不是數(shù)學(xué)意義上的相等,所以在用于Sorted Map和Sorted Set這些和比較有關(guān)系的數(shù)據(jù)結(jié)構(gòu)時需要特別小心。

 

在論文注釋的指引下,我們可以整理出BigDecimal類的脈絡(luò):

BigDecimal的介紹和使用

 

接下來我們就順著脈絡(luò)一點點的解剖這個龐然大物了。

基本屬性

從圖中可以看出BigDecimal類主要需要關(guān)注5個主要屬性

? intVal和scale

分別表示BigDecimal的無標(biāo)度值和標(biāo)度,結(jié)合我們在注釋里看到的說法“BigDecimal可以表示為一個任意精度的無刻度值和一個32位整型的刻度”,這兩個屬性可以認(rèn)為是BigDecimal類的骨架。

? precision

BigDecimal中數(shù)字的個數(shù),在確定了precision后就會要求結(jié)合Rounding Mode做一些舍入方面的操作。

? stringCache

BigDecimal的字符表示,在toString方法的時候用到。

? intCompact

無標(biāo)度值的Long表示,方便后續(xù)計算。如果intVal在compact的過程發(fā)現(xiàn)超過Long.MAX_VALUE則將intCompact記為Long.MIN_VALUE。

我們以三個例子來說明BigDecimal對于以上屬性的定義

BigDecimal b1 = new BigDecimal(“3.1415926”);

BigDecimal的介紹和使用

從Debug的結(jié)果看,intVal為空,因為無標(biāo)度值可以被壓縮存儲到intCompact中,precision表示有8個數(shù)字位,scale表示標(biāo)度為7

BigDecimal b2 = new BigDecimal(“31415926314159263141592631415926”);

BigDecimal的介紹和使用

intVal記錄的是無標(biāo)度值,這時候由于無標(biāo)度值超過了Long.MAX_VALUE,intCompact存儲了Long.MIN_VALUE,precision表示當(dāng)前數(shù)字位為32個,scale為0表示沒有小數(shù)位。

MathContext mc3 = new MathContext(30,RoundingMode.HALF_UP);
BigDecimal b2 = new BigDecimal(“31415926314159263141592631415926”);

BigDecimal的介紹和使用 

在這里我們手動設(shè)置了precision為30,所以最后兩位被丟棄并執(zhí)行了舍入操作,同時scale記錄為-2表示無標(biāo)度值表示到小數(shù)點左邊兩位。

         

通過上面三個例子我們對BigDecimal的5個基本屬性總結(jié)如下。

BigDecimal是通過unscaled value和scale來構(gòu)造,同時使用Long.MAX_VALUE作為我們是否壓縮的閾值。當(dāng)unscaled value超過閾值時采用intVal字段存儲unscaled value,intCompact字段存儲Long.MIN_VALUE,否則對unscaled value進行壓縮存儲到long型的intCompact字段用于后續(xù)計算,intVal為空。

scale字段存儲標(biāo)度,可以理解為unscaled value最后一位到實際值小數(shù)點的距離。如例1中對于3.1415926來說unscaled value為31415926,最后一位6到實際值的小數(shù)點距離為7,scale記為7;對于例3中手動設(shè)置precision的情況,unscaled value為31415926xxx159的最后一位9到實際值31415926xxx15900的小數(shù)點距離為2,由于在小數(shù)點左邊scale則記為-2。

precision字段記錄的是unscaled value的數(shù)字個數(shù),當(dāng)手動指定MathContext并且指定的precision小于實際precision的時候,會要求進行rounding操作。

 

創(chuàng)建函數(shù)

提到如何創(chuàng)建一個BigDecimal,首先想到的肯定是使用String參數(shù)的構(gòu)造函數(shù)進行構(gòu)建。

BigDecimal b = new BigDecimal(“3.14”);

實際上對于對象創(chuàng)建來說,BigDecimal提供了至少三種方式:

1, 構(gòu)造函數(shù)

BigDecimal提供了16個public的構(gòu)造函數(shù),支持通過char數(shù)組,String,double,BigInteger,long和int類型的參數(shù)構(gòu)造。

2, 工廠方法

BigDecimal主要通過valueOf方法提供對象的靜態(tài)工廠,支持通過double,BigInteger和long類型的參數(shù)構(gòu)造。具體用法:

BigDecimal f = BigDecimal.valueOf(1000L);

3, 對象緩存

對于常用的BigDecimal對象,內(nèi)部通過數(shù)組進行緩存,并開放了ZERO,ONE和TEN三個對象供使用端復(fù)用。具體用法:

BigDecimal c = BigDecimal.ZERO;

 

接下來具體看看三種創(chuàng)建方式的實現(xiàn)方式。

構(gòu)造函數(shù)

首先看看BigDecimal類提供的私有構(gòu)造函數(shù)。

/**
     * Trusted package private constructor.
     * Trusted simply means if val is INFLATED, intVal could not be null and
     * if intVal is null, val could not be INFLATED.
     */
    BigDecimal(BigInteger intVal, long val, int scale, int prec) {
        this.scale = scale;
        this.precision = prec;
        this.intCompact = val;
        this.intVal = intVal;
    }

從這個私有構(gòu)造函數(shù)可以看出BigDecimal對象主要關(guān)注的屬性字段,如果可以準(zhǔn)確的給這些屬性字段賦值則可以成功構(gòu)造一個BigDecimal對象。

這里我們可以大膽猜測其他公共的構(gòu)造函數(shù)和工廠方法內(nèi)部的邏輯都是計算這些屬性字段。

 

從我們的脈絡(luò)圖上看,構(gòu)造函數(shù)分為字符構(gòu)造和數(shù)值構(gòu)造。

字符構(gòu)造函數(shù)

對于字符構(gòu)造我們只需要關(guān)注兩個構(gòu)造函數(shù)即可:

1, public BigDecimal(char[] in, int offset, int len, MathContext mc)

從規(guī)模上看這個構(gòu)造函數(shù)是所有字符構(gòu)造函數(shù)中方法體最大的,同時結(jié)合其他字符構(gòu)造函數(shù)的邏輯可以發(fā)現(xiàn)這個構(gòu)造函數(shù)正是字符構(gòu)造函數(shù)的核心邏輯實現(xiàn)。

2, public BigDecimal(String val)

之所以關(guān)注這個構(gòu)造函數(shù),一方面是實際應(yīng)用的比較多,再者這個構(gòu)造函數(shù)的100行注釋也表明了官方對于這個構(gòu)造函數(shù)的推薦程度。

 

接下來我們集中攻克字符構(gòu)造函數(shù)的核心實現(xiàn),我們結(jié)合源代碼以程序流的方式進行說明。

 

第一步:處理符號位,如果是符號位則設(shè)置isneg字段并將offset往后移動一位

            // handle the sign
            boolean isneg = false;          // assume positive
            if (in[offset] == '-') {
                isneg = true;               // leading minus means negative
                offset++;
                len--;
            } else if (in[offset] == '+') { // leading + allowed
                offset++;
                len--;
            }

 

第二步,針對可壓縮的情況,遍歷字符進行分別處理。

2 如果是字符0判斷了兩種情況來處理prec和compact value的賦值,主要解決”00”這種多個0的無意義輸入。

1) 第一位數(shù)字為0,則直接將prec設(shè)置為1

2) 非第一位數(shù)字為0,則判斷之前的數(shù)值是否為0,如果為0則表明前面的數(shù)字是0,當(dāng)前數(shù)字不予處理;如果不為0則將數(shù)值乘以10,prec加1

                    if ((c == '0')) { // have zero
                        if (prec == 0)
                            prec = 1;
                        else if (rs != 0) {
                            rs *= 10;
                            ++prec;
                        } // else digit is a redundant leading zero
                        if (dot)
                            ++scl;
                    }

2 如果是字符1-9的情況,同樣處理了prec和compact value的賦值,主要考慮解決”01”這種以0開頭的數(shù)字的prec問題。

                   else if ((c >= '1' && c <= '9')) { // have digit
                        int digit = c - '0';
                        if (prec != 1 || rs != 0)
                            ++prec; // prec unchanged if preceded by 0s
                        rs = rs * 10 + digit;
                        if (dot)
                            ++scl;
                    }

2 如果是字符”.”的情況,主要解決出現(xiàn)了多個小數(shù)點的情況。

2 如果是Unicode或者其他格式的字符表示,通過Character.isDigit方法進行判斷,判斷完并完成轉(zhuǎn)換后將上面0和1-9的邏輯再走一遍,有點重復(fù)代碼的嫌疑。

2 如果是字符”e”和”E”,解析出e后面的數(shù)字用于后面計算scale

 

第三步,結(jié)合之前字符解析得到的prec和MathContext設(shè)置的prec進行rounding操作。主要邏輯是通過相差的prec算出一個drop,然后使用compact value和drop去做除法,比如需要drop 3位,那么就拿compact value和1000去做除法,并結(jié)合Rounding Mode判斷結(jié)果是否需要加1。

由于rounding之后可能存在進位問題,這里使用while循環(huán)來進行檢查。

                int mcp = mc.precision;
                int drop = prec - mcp;
                if (mcp > 0 && drop > 0) {  // do rounding
                    while (drop > 0) {
                        scl = checkScaleNonZero((long) scl - drop);
                        rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);
                        prec = longDigitLength(rs);
                        drop = prec - mcp;
                    }
                }

第四步,針對不可壓縮的情況,引入一個char數(shù)組容器用于構(gòu)建BigInteger類型的intValue。其他對于字符的處理以及如何設(shè)置prec,scale以及如何處理rounding和數(shù)值可壓縮的情況基本一致。

 

至此我們對于字符構(gòu)造函數(shù)的分析已經(jīng)結(jié)束,我們可以發(fā)現(xiàn)對于String類型的構(gòu)造函數(shù),我們其實是首先將String轉(zhuǎn)換成數(shù)組類型char[],然后調(diào)用字符數(shù)組構(gòu)造函數(shù)。所以出于性能考慮,如果我們的應(yīng)用場景里面獲取的是char[],可以直接調(diào)用字符數(shù)組構(gòu)造函數(shù),沒有必要先轉(zhuǎn)成String再去調(diào)用String構(gòu)造函數(shù),以至于白白損耗了兩次轉(zhuǎn)換的性能。

數(shù)值構(gòu)造函數(shù)

在數(shù)值構(gòu)造函數(shù)中,我們重點關(guān)注double類型的構(gòu)造函數(shù),因為這是在日常使用中最容易出問題的地方。

其他構(gòu)造函數(shù)的主要邏輯重點在于rounding和對于四個核心屬性的賦值,這點可以在字符構(gòu)造函數(shù)和后續(xù)的重點方法介紹中找到相應(yīng)的實現(xiàn)解析。

 

下面就讓我們集中火力攻克double構(gòu)造函數(shù)吧,同樣也是源代碼結(jié)合程序流的方式。

 

第一步,將double轉(zhuǎn)換成IEEE 754定義的浮點數(shù)bit表示方式,并通過位運算獲取到三個部分的值。

BigDecimal的介紹和使用

其中轉(zhuǎn)換成bit表示方式的方法是調(diào)用的虛擬機的native方法。

 

獲取sign的值比較好理解,右移63位后判斷值是否為0來確定數(shù)值的正負(fù)。

int sign = ((valBits >> 63) == 0 ? 1 : -1);

 

對于exponent和significand的邏輯就比較復(fù)雜了,首先明確目標(biāo)是將這個double表示為以下格式val == sign * significand * 2^exponent,再來看代碼:

int exponent = (int) ((valBits >> 52) & 0x7ffL);
long significand = (exponent == 0
                ? (valBits & ((1L << 52) - 1)) << 1
                : (valBits & ((1L << 52) - 1)) | (1L << 52));
exponent -= 1075;

要看懂這段代碼我們首先需要了解IEE754在浮點數(shù)轉(zhuǎn)換的幾點約定:

2 小數(shù)點左邊隱含一位,通常是1

2 單精度偏移量127,雙精度偏移量是1023

這時候回頭來看這段代碼,在計算significand的時候分成了兩種情況,當(dāng)exponent為0的時候直接進行左移右邊補0否則在左邊補1,都是為了補齊52個有效位和一個隱含位。

exponent需要偏移1075 = 1023 + 52,來源于自身的1023偏移量加上52位的有效位偏移。

 

第二步,將significand進行格式化,去除低位的0

        while ((significand & 1) == 0) { // i.e., significand is even
            significand >>= 1;
            exponent++;
        }

 

第三步,計算intVal和scale

        BigInteger intVal;
        long compactVal = sign * significand;
        if (exponent == 0) {
            intVal = (compactVal == INFLATED) ? INFLATED_BIGINT : null;
        } else {
            if (exponent < 0) {
                intVal = BigInteger.valueOf(5).pow(-exponent).multiply(compactVal);
                scale = -exponent;
            } else { //  (exponent > 0)
                intVal = BigInteger.valueOf(2).pow(exponent).multiply(compactVal);
            }
            compactVal = compactValFor(intVal);
        }

計算的時候按照exponent分成三種情況,

exponent==0,直接計算intVal

exponent<0,表明存在小數(shù)位,由于二進制數(shù)0.1對應(yīng)的十進制為0.5,所以小數(shù)位的轉(zhuǎn)換是5作為底

exponent>0,表明需要要在右邊補充0,二進制數(shù)1.0對應(yīng)的十進制為2,所以整數(shù)位的轉(zhuǎn)換是2作為底。

 

第四步,根據(jù)MathContext進行rounding操作,獲取precision,intValue和compact value。這一步是通用操作,就不做過多表述。

 

至此對于數(shù)值構(gòu)造函數(shù)的分析已經(jīng)結(jié)束。我們主要分析了double類型的構(gòu)造函數(shù),從代碼和程序流程可以看出double類型的構(gòu)造函數(shù)首先將double轉(zhuǎn)換成IEEE標(biāo)準(zhǔn)的二進制表示形式并分離出符號位、指數(shù)位和有效位,然后計算出precision、scale、intVal和compactVal來表示一個BigDecimal。由于小數(shù)轉(zhuǎn)二進制存在誤差導(dǎo)致了這個構(gòu)造函數(shù)構(gòu)造出的BigDecimal對象和實際值之間存在誤差,這也是為什么double類型的構(gòu)造函數(shù)不推薦使用的原因。

 

工廠函數(shù)

BigDecimal的工廠函數(shù)是通過靜態(tài)的valueOf方法提供的,主要針對long,BigInteger和double類型的參數(shù)。

由于long和BigInteger的數(shù)據(jù)類型和BigDecimal中的intValue和intCompact匹配,所以對于這兩種類型的工廠方法實現(xiàn)相對簡單,主要就是四個屬性的賦值。

而在double類型的工廠方法中,使用了和構(gòu)造函數(shù)完全不同的構(gòu)造邏輯:

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

這里通過調(diào)用Double的toString方法首先將double轉(zhuǎn)換成字符串然后再調(diào)用字符構(gòu)造函數(shù),從而避免了精度丟失的問題,所以在注釋中也提示了使用者:如果一定要用double來構(gòu)造BigDecimal對象優(yōu)先使用工廠方法。


當(dāng)前名稱:BigDecimal的介紹和使用
網(wǎng)頁路徑:http://weahome.cn/article/gpospi.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部