您好,登錄后才能下訂單哦!
算術(shù)運(yùn)算
作為一個(gè)數(shù)值類型,算術(shù)運(yùn)算是基本功能。相應(yīng)的BigDecimal也提供了基本的算術(shù)運(yùn)算如加減乘除,還有一些高級(jí)運(yùn)算如指數(shù)運(yùn)算pow、絕對(duì)值abs和取反negate等。我們重點(diǎn)分析比較常用的加減乘除和指數(shù)函數(shù)pow。
在加法運(yùn)算上BigDecimal提供了兩個(gè)public的方法。
1, public BigDecimal add(BigDecimal augend)。
這個(gè)方法采用的邏輯比較簡(jiǎn)單,他遵循了我們對(duì)BigDecimal的最初認(rèn)識(shí),即只要搞定四個(gè)基本屬性,這個(gè)對(duì)象就搞定了。所以在邏輯上的實(shí)現(xiàn)方式如下:
result.intValue/intCompact = this.intValue/intCompact + augend. intValue/intCompact result.scale = max(this.scale, augend.scale) result.precision = 0
2, public BigDecimal add(BigDecimal augend, MathContext mc)
這個(gè)方法和上面的方法只相差一個(gè)MathContext參數(shù),依照我們之前的經(jīng)驗(yàn),這個(gè)應(yīng)該是在第一個(gè)方法的基礎(chǔ)上加入了Rounding相關(guān)的操作。事實(shí)的確如此,唯一的差異是針對(duì)零值的情況加入了處理片段。
BigDecimal對(duì)于減法同樣提供了兩個(gè)public的方法,對(duì)應(yīng)于加法的兩個(gè)方法。在處理邏輯上完全復(fù)用了加法的處理邏輯,針對(duì)減數(shù)進(jìn)行了negate取負(fù)操作。
public BigDecimal subtract(BigDecimal subtrahend, MathContext mc) { if (mc.precision == 0) return subtract(subtrahend); // share the special rounding code in add() return add(subtrahend.negate(), mc); }
乘法運(yùn)算和加法運(yùn)算的思想保持一致,采用的邏輯為:
result.intValue/intCompact = this.intValue/intCompact * multiplicand. intValue/intCompact result.scale = sum(this.scale, multiplicand.scale) result.precision = 0
在實(shí)際實(shí)現(xiàn)過(guò)程中提供了兩類方法(之所以是兩類,是因?yàn)榇嬖趨?shù)不同的重載),分別為mutiply和multiplyAndRound。
除法運(yùn)算采用的邏輯為:
result.intValue/intCompact = this.intValue/intCompact 除以 divisor. intValue/intCompact
result.scale = this.scale - divisor.scale
BigDecimal的除法運(yùn)算提供了5個(gè)的public方法,但是具體實(shí)現(xiàn)只有兩個(gè),接下來(lái)我們具體看一下這兩個(gè)實(shí)現(xiàn)。
1, public BigDecimal divide(BigDecimal divisor)
這個(gè)方法在實(shí)際使用中并不多,因?yàn)檫@個(gè)方法要求滿足整除的條件,如果不能整除則會(huì)拋出異常。
MathContext mc = new MathContext( (int)Math.min(this.precision() + (long)Math.ceil(10.0*divisor.precision()/3.0), Integer.MAX_VALUE), RoundingMode.UNNECESSARY); BigDecimal quotient; try { quotient = this.divide(divisor, mc); } catch (ArithmeticException e) { throw new ArithmeticException("Non-terminating decimal expansion; " + "no exact representable decimal result."); }
從上面的代碼可以看出來(lái),對(duì)于沒(méi)有指定MathContext的情況會(huì)定義一個(gè)用于計(jì)算的MathContext,其中的precision為:
Math.min(this.precision() + (long)Math.ceil(10.0*divisor.precision()/3.0),Integer.MAX_VALUE)
并且RoundingMode為UNNECESSARY。
這樣在無(wú)法整除的情況下,precision必然會(huì)超過(guò)定義的precision,同時(shí)由于RoundingMode的定義無(wú)法得知Rounding規(guī)則,此時(shí)拋出異常是合理的。
2, public BigDecimal divide(BigDecimal divisor, MathContext mc)
在具體實(shí)現(xiàn)的過(guò)程中會(huì)根據(jù)除數(shù)和被除數(shù)的類型,分別調(diào)用底層關(guān)于long和BigInteger的實(shí)現(xiàn),最終的實(shí)現(xiàn)是通過(guò)方法divideAndRound來(lái)實(shí)現(xiàn)的。我們主要看兩個(gè)實(shí)現(xiàn)
2 private static BigDecimal divideAndRound(long ldividend, long ldivisor, int scale, int roundingMode, int preferredScale)
當(dāng)除數(shù)和被除數(shù)都是long類型的情況,首先找出quotient和remainder
long q = ldividend / ldivisor; long r = ldividend % ldivisor;
根據(jù)除數(shù)和被除數(shù)的符號(hào)來(lái)獲取結(jié)果的符號(hào)
qsign = ((ldividend < 0) == (ldivisor < 0)) ? 1 : -1;
如果remainder不為0則需要處理rounding進(jìn)位問(wèn)題
if (r != 0) { boolean increment = needIncrement(ldivisor, roundingMode, qsign, q, r); return valueOf((increment ? q + qsign : q), scale); }
如果remainder為0則直接對(duì)于scale進(jìn)行處理即可。
2 private static BigDecimal divideAndRound(BigInteger bdividend, BigInteger bdivisor, int scale, int roundingMode, int preferredScale)
當(dāng)除數(shù)和被除數(shù)都是BigInteger的情況,我們的處理流程和long相似,不同點(diǎn)在于BigInteger和long的差異。
對(duì)于找出quotient和remainder,long類型可以直接使用算術(shù)運(yùn)算符,而BigInteger需要使用MutableBigInteger的divide方法
MutableBigInteger mdividend = new MutableBigInteger(bdividend.mag); MutableBigInteger mq = new MutableBigInteger(); MutableBigInteger mdivisor = new MutableBigInteger(bdivisor.mag); MutableBigInteger mr = mdividend.divide(mdivisor, mq);
獲取符號(hào)位的時(shí)候通過(guò)方法位而不是直接和0比較
qsign = (bdividend.signum != bdivisor.signum) ? -1 : 1;
其他操作也是相當(dāng)于做一次long到BigInteger的遷移,不做贅述。
指數(shù)運(yùn)算同樣提供兩個(gè)public的方法實(shí)現(xiàn)。
1, public BigDecimal pow(int n)
方法邏輯比較簡(jiǎn)單,通過(guò)計(jì)算unscaled value和scale來(lái)構(gòu)造結(jié)果的BigDecimal。
其中unscaled value通過(guò)BigInteger的pow方法直接計(jì)算,scale則利用this.scale *n來(lái)表示。
if (n < 0 || n > 999999999) throw new ArithmeticException("Invalid operation"); // No need to calculate pow(n) if result will over/underflow. // Don't attempt to support "supernormal" numbers. int newScale = checkScale((long)scale * n); return new BigDecimal(this.inflated().pow(n), newScale);
在使用的時(shí)候注意n的取值范圍即可。
2, public BigDecimal pow(int n, MathContext mc)
按照一般規(guī)律,這個(gè)方法的邏輯應(yīng)該是在上一個(gè)的基礎(chǔ)上對(duì)結(jié)果的BigDecimal進(jìn)行rounding即可。然而事實(shí)上并不是,在實(shí)際實(shí)現(xiàn)中引入了X3.274-1996算法,計(jì)算邏輯如下:
int mag = Math.abs(n); // ready to carry out power calculation... BigDecimal acc = ONE; // accumulator boolean seenbit = false; // set once we've seen a 1-bit for (int i=1;;i++) { // for each bit [top bit ignored] mag += mag; // shift left 1 bit if (mag < 0) { // top bit is set seenbit = true; // OK, we're off acc = acc.multiply(lhs, workmc); // acc=acc*x } if (i == 31) break; // that was the last bit if (seenbit) acc=acc.multiply(acc, workmc); // acc=acc*acc [square] // else (!seenbit) no point in squaring ONE } // if negative n, calculate the reciprocal using working precision if (n < 0) // [hence mc.precision>0] acc=ONE.divide(acc, workmc);
計(jì)算過(guò)程我們可以分解如下:
2 mag += mag相當(dāng)于對(duì)n做左移操作
2 if(mag <0) 表示左移之后的首位為1,這個(gè)時(shí)候首先乘以當(dāng)前BigDecimal然后通過(guò)標(biāo)志位seenbit做平方操作
2 針對(duì)最后一位的1 = 2^0*1,所以只要乘一次不需要后面的平方操作所以在i=31的情況下跳出循環(huán)
2 最后判斷n<0的情況用1除以當(dāng)前累積值取倒數(shù)
我們以12的5次方來(lái)說(shuō)明以上過(guò)程
1, 對(duì)于5做左移操作,得到第一個(gè)標(biāo)識(shí)位的時(shí)候?yàn)?/span>101000…,此時(shí)i=29
2, mag <0 => seenbit = true, acc = 1*12 = 12(12^1)
3, seenbit = true => acc = 12 * 12 = 144(12^2)
4, 左移 i= 30, mag 的值為01000…
5, mag>0 => seenbit值不變還是true
6, seenbit =true => acc = acc * acc = 144* 144(12^4)
7, 左移i=31,mag的值為1000…
8, mag<0=>seenbit = true,acc = acc * 12 = 144*144*12(12^5)
9, i = 31 => 跳出循環(huán)
關(guān)鍵方法是指在構(gòu)造方法和算術(shù)運(yùn)算中會(huì)涉及到的方法和使用中用的比較多的方法,如果這個(gè)方法的邏輯構(gòu)思比較值得解析,我們會(huì)在下面羅列出來(lái)進(jìn)行深入了解。
doRound方法也屬于關(guān)鍵方法,只不過(guò)在構(gòu)造函數(shù)部分已經(jīng)對(duì)于實(shí)現(xiàn)邏輯進(jìn)行了說(shuō)明,這里不再列出來(lái)。
setScale方法用于重新設(shè)置BigDecimal對(duì)象的標(biāo)度,根據(jù)我們之前的理解BigDecimal四大屬性(intVal, intCompact,precision,scale)都會(huì)相應(yīng)的受到影響,如scale變化則unscaled value會(huì)相應(yīng)的通過(guò)乘或者除進(jìn)行調(diào)整。
需要注意的是BigDecimal對(duì)象是不可變的,所以這個(gè)方法不會(huì)直接去修改當(dāng)前對(duì)象而是返回一個(gè)新的對(duì)象。
我們以public BigDecimal setScale(int newScale, int roundingMode)作為分析對(duì)象。
在實(shí)現(xiàn)邏輯上按照unscaled value的范圍分成兩個(gè)處理分支:
1, 通過(guò)intCompact存儲(chǔ)unscaled value
根據(jù)前后scale判斷是做乘或者除,如果是乘則需要考慮超過(guò)Long.MAX_VALUE的情況,如果是除則直接調(diào)用divideAndRound方法。
2, 通過(guò)intVal存儲(chǔ)unscaled value
邏輯和intCompact存儲(chǔ)類似,差別在于調(diào)用的乘和除的方法都是適用于BigInteger而上面是適用于long。
compareTo方法用于兩個(gè)BigDecimal的比較是比較頻繁使用的方法。我們使用源代碼和程序流的方式來(lái)分析邏輯。
if (scale == val.scale) { long xs = intCompact; long ys = val.intCompact; if (xs != INFLATED && ys != INFLATED) return xs != ys ? ((xs > ys) ? 1 : -1) : 0; }
這里提供了一個(gè)快速返回路徑,針對(duì)兩個(gè)比較的對(duì)象標(biāo)度一致的情況。由于BigDecimal可以表示成unscaled value和scale的形式,所以在scale相等的情況下我們只需要比較unscaled value即可。在這個(gè)快速返回路徑中僅僅比較了intCompact存在的情況,long類型直接使用算術(shù)
算術(shù)比較符比較即可。
再看比較的主邏輯,
int xsign = this.signum(); int ysign = val.signum(); if (xsign != ysign) return (xsign > ysign) ? 1 : -1;
首先獲取符號(hào)位,如果符號(hào)位不等則根據(jù)符號(hào)位的大小就可以得到結(jié)果。
if (xsign == 0) return 0;
如果符號(hào)位都等于0,則表明兩個(gè)對(duì)象都為0,返回相等的結(jié)果。
int cmp = compareMagnitude(val);
我們?cè)倏纯?/span>compareMagnitude里面的實(shí)現(xiàn),
long xae = (long)this.precision() - this.scale; // [-1] long yae = (long)val.precision() - val.scale; // [-1] if (xae < yae) return -1; if (xae > yae) return 1;
這又是一個(gè)快速返回路徑,通過(guò)precision – scale可以獲得整數(shù)位的長(zhǎng)度,根據(jù)長(zhǎng)度可以快速比較出大小。
接下來(lái)根據(jù)算出來(lái)的sdiff對(duì)scale較小的進(jìn)行乘十運(yùn)算使得比較的雙方在scale上沒(méi)有差異,這時(shí)候再調(diào)用BigInteger的compareMagnitude方法比較
for (int i = 0; i < len1; i++) { int a = m1[i]; int b = m2[i]; if (a != b) return ((a & LONG_MASK) < (b & LONG_MASK)) ? -1 : 1; }
這里是按順序比較每個(gè)int的大小,由于比較是基于無(wú)符號(hào)的所以需要與LONG_MASK進(jìn)行按位與操作將首位置為0.
return (xsign > 0) ? cmp : -cmp;
最終我們根據(jù)符號(hào)位和上面的比較結(jié)果確定輸出結(jié)果是否需要處理。
equals方法在我們很多數(shù)據(jù)結(jié)構(gòu)中都會(huì)隱式的調(diào)用,如ArrayList的contains方法。而在BigDecimal的equals中就隱藏著一個(gè)大坑,直接上代碼:
if (scale != xDec.scale) return false;
看這個(gè)快速返回塊,如果這兩個(gè)BigDecimal的scale不相等則判定BigDecimal不相等,這樣導(dǎo)致的結(jié)果是BigDecimal(“0”)和BigDecimal(“0.0”) 這兩個(gè)數(shù)居然不相等,測(cè)試如下:
BigDecimal a = new BigDecimal("0.0"); BigDecimal b = BigDecimal.ZERO; System.out.print(a.equals(b));//false
這里也印證了前面提過(guò)的compareTo和equals不等價(jià)的說(shuō)法,坑反正已經(jīng)在了,咱別踩就行。
我們通過(guò)BigDecimal的注釋整理出BigDecimal類的脈絡(luò),然后按照基本屬性、創(chuàng)建函數(shù)、算術(shù)運(yùn)算和關(guān)鍵方法的順序進(jìn)行各個(gè)擊破。
通過(guò)基本屬性的解析我們了解了BigDecimal類的骨架就是unscaled value和scale,由此可以推斷出BigDecimal的上下限是由BigInteger和int的上下限決定。
在創(chuàng)建函數(shù)部分,除了我們的老朋友 – 基于字符型的構(gòu)造函數(shù),我們還認(rèn)識(shí)了基于數(shù)值型的構(gòu)造函數(shù)并且知道了為什么double型的構(gòu)造函數(shù)會(huì)出現(xiàn)小數(shù)精度問(wèn)題,這個(gè)問(wèn)題在工廠函數(shù)中使用了Double的toString方法進(jìn)行解決。
算術(shù)運(yùn)算的核心是計(jì)算核心屬性,通過(guò)BigInteger和Long的相應(yīng)方法計(jì)算出unscaled value,通過(guò)算術(shù)運(yùn)算對(duì)于scale的約定計(jì)算出scale,最后結(jié)合MathContext的設(shè)置進(jìn)行rounding操作。在除法運(yùn)算中需要注意整除的問(wèn)題,對(duì)于不確定能不能整除的情況一定要指定MathContext,否則可能拋出異常。在指數(shù)運(yùn)算中引入了X3.274-1996算法,使用位移的方式來(lái)進(jìn)行指數(shù)計(jì)算。
我們通過(guò)整個(gè)類的解讀識(shí)別出了doRound,setScale,compareTo和equals這四個(gè)關(guān)鍵方法,其中doRound和setScale是顯式調(diào)用比較多的,而compareTo和equals是比較容易出問(wèn)題的方法。特別需要注意的是equals的相等和compareTo返回的0并不等價(jià)。
通過(guò)以上的解讀,相信大部分的人都可以毫無(wú)心理壓力的使用BigDecimal了。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。