1 public BigDecimal(char[] in, int offset, int len, MathContext mc) {// 使用字符數組的構造方法,一般我們推薦使用的是一String類為參數的構造以保證精度不會丟失 2 // protect against huge length. 3 if (offset + len > in.length || offset < 0)// 索引偏移量不能為負數且索引偏移量與使用的字符數之和不能超過字符數組的長度,否則拋出數字格式化異常 4 throw new NumberFormatException("Bad offset or len arguments for char[] input."); 5 // This is the primary string to BigDecimal constructor; all 6 // incoming strings end up here; it uses explicit (inline) 7 // parsing for speed and generates at most one intermediate 8 // (temporary) object (a char[] array) for non-compact case. 9 10 // Use locals for all fields values until completion 11 int prec = 0; // record precision value 12 int scl = 0; // record scale value 13 long rs = 0; // the compact value in long 14 BigInteger rb = null; // the inflated value in BigInteger,以上定義了4個變量用於最后對成員變量賦值使用,若值未改變則世界使用當前默認值 15 // use array bounds checking to handle too-long, len == 0, 16 // bad offset, etc. 17 try { 18 // handle the sign,首先處理第一個字符,因其可能為符號位需要特殊處理 19 boolean isneg = false; // assume positive,先假定為正數,因此定義一個標志位isneg為false 20 if (in[offset] == '-') { 21 isneg = true; // leading minus means negative 22 offset++; 23 len--;// 若第一個字符為'-'號,表明該數值為負數,標志位isneg為true,同時將索引偏移量自增,將需要的字符個數自減 24 } else if (in[offset] == '+') { // leading + allowed 25 offset++; 26 len--;// 若第一個字符為'+'號,則不需要改變標志位,將索引偏移量自增,將需要的字符個數自減 27 } 28 29 // should now be at numeric part of the significand,接下來處理數組的數值部分字符 30 boolean dot = false; // true when there is a '.',首先定義小數點標志位dot,默認為false 31 long exp = 0; // exponent,定義指數值exp為0 32 char c; // current character,定義當前字符c,用於循環使用 33 boolean isCompact = (len <= MAX_COMPACT_DIGITS);// MAX_COMPACT_DIGITS為常量18,用於判斷最終數值的數字位數,若小於等於18,則該數值的數值部分(不考慮小數點)可以使用long類型表示 34 // integer significand array & idx is the index to it. The array 35 // is ONLY used when we can't use a compact representation. 36 int idx = 0; 37 if (isCompact) {// len小於等於18 38 // First compact case, we need not to preserve the character 39 // and we can just compute the value in place. 40 for (; len > 0; offset++, len--) {// 開啟循環取出字符數組中的數值 41 c = in[offset];// c代表當前字符 42 if ((c == '0')) { // c為'0' 43 if (prec == 0)// 若有效數位為0,則賦值為1(剛開始覺得這里有問題,如果第一個字符就是0則有效位數不應該自增才對,這里其實與第二個if語句體對應,且往下看) 44 prec = 1; 45 else if (rs != 0) {// 若有效數位不為0,rs也為0,將rs變為原值的10倍(即上升一個進位制),並將有效數位自增 46 rs *= 10; 47 ++prec; 48 } // else digit is a redundant leading zero 49 if (dot)// 若之前循環出現過字符'.'也就是小數點,則將有效小數位自增 50 ++scl; 51 } else if ((c >= '1' && c <= '9')) { // 若c為數值1~9,計算字符的實際映射的數值並賦值給digit變量 52 int digit = c - '0'; 53 if (prec != 1 || rs != 0)// 不執行此步驟出現的情況:字符數組前幾位均為字符'0',此時出現第一個數值字符時有效數值位數不需要改變(因為在這種情況下,在第一次出現'0'時已經提前增加了有效數位prec的值) 54 ++prec; // prec unchanged if preceded by 0s,這一個與上邊的疑問對應,若在第一個數值字符出現之前有多個'0'字符出現,則有效位數一定為1,此時當前數值字符出現時我們不改變prec的值 55 rs = rs * 10 + digit;// 十進制計算數值 56 if (dot) 57 ++scl; 58 } else if (c == '.') { // 當前字符為'.'小數點 59 // have dot 60 if (dot) // two dots,之前循環已經存在一個小數點則目前有兩個則拋出異常 61 throw new NumberFormatException(); 62 dot = true; 63 } else if (Character.isDigit(c)) { // 字符是其他類型的數字(Unicode編碼的非阿拉伯數字) 64 int digit = Character.digit(c, 10);// 獲取字符所映射的數值,一下步驟與上面的分析是一樣的 65 if (digit == 0) { 66 if (prec == 0) 67 prec = 1; 68 else if (rs != 0) { 69 rs *= 10; 70 ++prec; 71 } // else digit is a redundant leading zero 72 } else { 73 if (prec != 1 || rs != 0) 74 ++prec; // prec unchanged if preceded by 0s 75 rs = rs * 10 + digit; 76 } 77 if (dot) 78 ++scl; 79 } else if ((c == 'e') || (c == 'E')) {// 若當前字符是'e'或'E',則代表科學計數法 80 exp = parseExp(in, offset, len);// 解析字符'e'或'E'之后的指數值后,若通過校驗則結束循環 81 // Next test is required for backwards compatibility 82 if ((int) exp != exp) // overflow,溢出(已經超出int的表數范圍)則拋出格式化異常,一般指數也不會這么大int可以大概表數正負21億 83 throw new NumberFormatException(); 84 break; // [saves a test] 85 } else {// 當前字符不是數值或者指數標識符拋出異常 86 throw new NumberFormatException(); 87 } 88 } 89 if (prec == 0) // no digits found,循環結束后若一個數值(包括'0')也沒找到則拋出格式化異常, 90 throw new NumberFormatException(); 91 // Adjust scale if exp is not zero. 92 if (exp != 0) { // had significant exponent,科學計數法表達式指數不為0則需要調整有效小數位數 93 scl = adjustScale(scl, exp);// 特別的有效小數位數可以為負數 94 } 95 rs = isneg ? -rs : rs;// 應用數值符號標志位恢復正負 96 int mcp = mc.precision;// 獲取MathContext上下文的有效位數 97 int drop = prec - mcp; // prec has range [1, MAX_INT], mcp has range [0, MAX_INT]; 98 // therefore, this subtract cannot overflow 99 if (mcp > 0 && drop > 0) { // do rounding,若上下文的有效位數>0,且解析出的數值的有效位數大於上下文的有效位數則需要舍入 100 while (drop > 0) { 101 scl = checkScaleNonZero((long) scl - drop);// 根據原有效小數位數與需要舍去的位數對有效小數位數進行修正 102 rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode);// 根據舍入模式與需要舍入的位數對rs進行舍入處理 103 prec = longDigitLength(rs);// longDigitLength()方法獲取long類型數值rs的有效數字位數 104 drop = prec - mcp;// 重新計算需要舍去的位數,若大於0則繼續循環 105 } 106 } 107 } else {// 若需要解析的數值位數(包括小數點與指數表達式大於18) 108 char coeff[] = new char[len];// 定義緩存字符數組coeff,長度等於需要解析的字符數量 109 for (; len > 0; offset++, len--) { 110 c = in[offset];// 獲取當前字符 111 // have digit 112 if ((c >= '0' && c <= '9') || Character.isDigit(c)) {// 當前字符為數字 113 // First compact case, we need not to preserve the character 114 // and we can just compute the value in place. 115 if (c == '0' || Character.digit(c, 10) == 0) { 116 if (prec == 0) {// 當前字符為0且當前有效位數為0(表明是第一次解析出'0'字符),則保存至緩存字符數組 117 coeff[idx] = c; 118 prec = 1;// 將有效位數置為1,(即若第一字符為'0'也將計入有效數位),但是idx索引並不更新 119 } else if (idx != 0) {// 此時表明解析出的0是"中間"的'0'字符,此時將保存至緩存字符數組 120 coeff[idx++] = c;// 更新緩存數組的索引 121 ++prec;// 更新有效位數 122 } // 其實還存在第三種情況,即繼首位為0之后連續存在多個0,此時什么也不做(不保存也不更新有效位數與緩存索引) 123 } else {// 當前字符是不為'0'的數字 124 if (prec != 1 || idx != 0)// 不執行此步驟唯一的情況是:首位或其后續連續幾位為'0',則此時prec與idx都是0(這與上面的疑問處的處理是一樣的,此時不增加有效位數) 125 ++prec; // prec unchanged if preceded by 0s 126 coeff[idx++] = c;// 只要出現非'0'數字都要保存至緩存字符數組 127 } 128 if (dot)// 若之前循環已經出現過'.'小數點,則有效小數位數自增,並且結束本次循環 129 ++scl; 130 continue; 131 } 132 // have dot 133 if (c == '.') {// 小數點,與上邊的處理相同 134 // have dot 135 if (dot) // two dots 136 throw new NumberFormatException(); 137 dot = true; 138 continue; 139 } 140 // exponent expected 141 if ((c != 'e') && (c != 'E'))// 若當前字符既不是數字也不是小數點也不是指數標志,則拋出數字格式化異常 142 throw new NumberFormatException(); 143 exp = parseExp(in, offset, len);// 否則解析指數表達式的值,方法與上邊相同 144 // Next test is required for backwards compatibility 145 if ((int) exp != exp) // overflow,指數溢出拋出異常 146 throw new NumberFormatException(); 147 break; // [saves a test] 148 } 149 // here when no characters left 150 if (prec == 0) // no digits found,為解析出數值則拋出異常 151 throw new NumberFormatException(); 152 // Adjust scale if exp is not zero. 153 if (exp != 0) { // had significant exponent,指數不為0則需要調整有效小數位數 154 scl = adjustScale(scl, exp);// 特別的有效小數位數可以為0(可以嘗試一下,存在指數表達式時) 155 } 156 // Remove leading zeros from precision (digits count) 157 rb = new BigInteger(coeff, isneg ? -1 : 1, prec);// 根據解析出來的字符緩存數組構建BigInteger對象,這個里邊大有文章,下片源碼解析在進行分析 158 rs = compactValFor(rb);// 根據創建的BigInteger對象獲取該對象所表示的數值(若可以表示使用long類型實際表示,否則使用long類型的最小值表示,例如超出表數范圍) 159 int mcp = mc.precision;// 獲取MathContext上下文中的有效小數位數(這個才是最終的有效小數位數,可以對原生的BigDecimal對象中的值的有效位數進行修改) 160 if (mcp > 0 && (prec > mcp)) {// 這個與上面的代碼分析差不多,即MathContext中的有效位數要小於原生生成的BigDecimal對象中值的有效位數,需要進行舍入操作 161 if (rs == INFLATED) {// 若rs表示的數值是long類型的最小值(即該BigInteger的數值已經超出long類型的表數范圍) 162 int drop = prec - mcp;// 獲取需要舍去的有效位數 163 while (drop > 0) {// 循環舍去直到drop小於等於0 164 scl = checkScaleNonZero((long) scl - drop);// 若原有效小數位數減去舍去的位數,超過int的表數范圍則拋出異常 165 rb = divideAndRoundByTenPow(rb, drop, mc.roundingMode.oldMode);// 進行舍去,見👇 166 rs = compactValFor(rb);// 獲取舍去后的BigInteger對象的值 167 if (rs != INFLATED) {// 獲取舍去后的有效長度並結束循環,為啥結束循環看下面會繼續處理 168 prec = longDigitLength(rs); 169 break; 170 } 171 prec = bigDigitLength(rb);// 否則獲取有效長度后(對比上下兩個獲取有效長度的方法,是不一樣的,rs是long類型的而rb是BigInteger類型的),獲取最新的實際有效位數與MathContext之間的差值 172 drop = prec - mcp; 173 } 174 } 175 if (rs != INFLATED) {// 若rs已經是long類型的表數范圍則進行一下處理,這個與處理字符長度小於等於18的那個分支是一樣的,並且將之前創建的BigInteger對象賦值為null 176 int drop = prec - mcp; 177 while (drop > 0) { 178 scl = checkScaleNonZero((long) scl - drop); 179 rs = divideAndRound(rs, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode); 180 prec = longDigitLength(rs); 181 drop = prec - mcp; 182 } 183 rb = null;// 在該數值不能使用long類型表示時,該值才不為null.也就是說在能使用long類型表示時,使用rs表示該數值此時rb為null,而不能使用long類型表示時,使用rb表時該值,此時rs也存在但是該值並無用 184 } 185 } 186 } 187 } catch (ArrayIndexOutOfBoundsException e) { 188 throw new NumberFormatException(); 189 } catch (NegativeArraySizeException e) { 190 throw new NumberFormatException(); 191 } 192 this.scale = scl; 193 this.precision = prec; 194 this.intCompact = rs; 195 this.intVal = rb; 196 }
獲取long類型整數的長度算法,看了好久
1 static int longDigitLength(long x) { 2 /* 3 * As described in "Bit Twiddling Hacks" by Sean Anderson, 4 * (http://graphics.stanford.edu/~seander/bithacks.html) 5 * integer log 10 of x is within 1 of (1233/4096)* (1 + 6 * integer log 2 of x). The fraction 1233/4096 approximates 7 * log10(2). So we first do a version of log2 (a variant of 8 * Long class with pre-checks and opposite directionality) and 9 * then scale and check against powers table. This is a little 10 * simpler in present context than the version in Hacker's 11 * Delight sec 11-4. Adding one to bit length allows comparing 12 * downward from the LONG_TEN_POWERS_TABLE that we need 13 * anyway. 14 */ 15 assert x != BigDecimal.INFLATED;// 斷言x不為long類型的最小值 16 if (x < 0) 17 x = -x;// 因為x不為long類型的最小值,因此可以這樣賦值而不超過long類型的表數范圍(long類型的表數范圍-2^63~2^63-1,若x為最小值-2^63,取相反數-x后再賦值給x將會溢出) 18 if (x < 10) // must screen for 0, might as well 10,針對0這種特殊情況需要特殊判斷 19 return 1; 20 int r = ((64 - Long.numberOfLeadingZeros(x) + 1) * 1233) >>> 12;// Long.numberOfLeadingZeros(x)方法獲取x的二進制下最高非0位之前0所占的位數,這條語句使用的公式就是lg(X) ~= lg2*(1+log2(X)),而lg2~=1233/4096,4096就是2^12次方
這個公式我們就不研究了,有興趣可以去上邊官方注釋中的那個網址中研究(英文的,祝各位好運),公式分析:lg(X)其實就是數X的十進制下的位數,log2(X)就是數X在二進制下的位數,那這個公式就可以理解為:long類型的整數X在十進制下的位數約等於1233/(2^12)乘以
(1+X在二進制下的位數). 21 long[] tab = LONG_TEN_POWERS_TABLE;// 整數數組,長度為19 22 // long類型的表數的最大值等於19位數值,等於數組LONG_TEN_POWERS_TABLE的長度,若r大於等於19,說明x的數值就是19位 23 return (r >= tab.length || x < tab[r]) ? r : r + 1; 24 }
獲取long類型數值在二進制下最高非0位左側的0所占的位數
1 public static int numberOfLeadingZeros(long i) { 2 // HD, Figure 5-6 3 if (i == 0)// 若i為0,則二進制下64位下均為0,所以返回64 4 return 64; 5 int n = 1; 6 int x = (int)(i >>> 32);// long類型的i右移32位並賦值給x,此時i原高位32位轉化為地位32位 7 if (x == 0) { n += 32; x = (int)i; }// 若此時x等於0則表明i原高位32位為0,則n自增32並將i強轉為int
// (這么做是因為已經判斷出i的高位32位全部為0,接下來需要判斷i的低位32位是否還有0的存在,將i強轉int后就會將高位32位直接截掉剩余低位32位)
// 若該語句未執行則表明i高位32位存在非0數,此時x即為i的高位32位以進行接下來的判斷 8 if (x >>> 16 == 0) { n += 16; x <<= 16; }// 接來下的操作與第一步區別不太大 9 if (x >>> 24 == 0) { n += 8; x <<= 8; } 10 if (x >>> 28 == 0) { n += 4; x <<= 4; } 11 if (x >>> 30 == 0) { n += 2; x <<= 2; } 12 n -= x >>> 31;// x為int類型,右移31位將只剩下最高位要么為0要么為1
// 若為0,因n之前直接賦值為1(即執行到上一步時其實n的值應必實際值多1),此時原n多出的1正好作為最高位0的計數:n -= 0
// 若為1,則表明x的最高位並不為0,英因此需要將原n多出的1減去:n -= 1 13 return n; 14 }
獲取指定BigInteger對象中的值
1 private static long compactValFor(BigInteger b) { 2 int[] m = b.mag;// 這個mag就是BigInteger中實際存儲數值的int類型的數組,而且需要注意的是其中元素也就是每個int的數值位數跟b的進位制有關, 3 特別的10進制的情況下,int的數位最多為9(因為int的表數范圍是-2^31~2^31-1,也就是正負21億,大概是10位,但是實際情況是99億也是10位的 4 但已經超出int的表數范圍,因此只能使用9位.而且從這一點我們也可以了解到BigInteger的mag屬性中就是使用分段式的int數值來表示大整數, 5 比如:12345678987654321在十進制下,在mag中是以{12345678,987654321}這種形式來保存的) 6 int len = m.length;// 獲取mag數組長度 7 if (len == 0)// 若長度為0則b就是0,直接返回0 8 return 0; 9 int d = m[0];// 否則獲取數組第一位int數值 10 if (len > 2 || (len == 2 && d < 0))// mag數組的長度大於2或者等於2但首位int為負數,返回INFLATED(長度大於2那數值為數至少是1+2*9=19位 11 超出long類型的表數范圍.其次因BigInteger的符號位是使用signnum屬性表示,其mag數組中應不存在負值) 12 return INFLATED; 13 14 long u = (len == 2)?// 若mag的長度為1或2則可以使用long類型表示BigInteger對象的存儲的數值 15 (((long) m[1] & LONG_MASK) + (((long)d) << 32)) : 16 (((long)d) & LONG_MASK);// LONG_MASK為long類型,二進制表示為所有位全部為1,這樣任意一個long類型的整數"X & LONG_MASK"表達式的 17 結果是就是X 18 return (b.signum < 0)? -u : u;// 將運算結果加上數值原符號 19 }
以十進制進行舍入(做除法進行舍入多余的有效數字)
1 private static BigInteger divideAndRoundByTenPow(BigInteger intVal, int tenPow, int roundingMode) {// intVal:需要進行進行舍入操作的原始數值
tenPow:10的次冪(2就代表10^2次方) roundingMode:舍入模式 2 if (tenPow < LONG_TEN_POWERS_TABLE.length)// LONG_TEN_POWERS_TABLE:指數與10的次冪的映射,指數映射數組索引而10的次冪映射數組的元素(例如10^3就代表該數組的索引為3的元素為10^3次方)
該數組長度為19,即最高位10^18次方,至於數組長度為什么是19這是因為long類型的表數范圍決定的
根據tenPow<19與否決定執行那個方法
3 intVal = divideAndRound(intVal, LONG_TEN_POWERS_TABLE[tenPow], roundingMode);
4 else
5 intVal = divideAndRound(intVal, bigTenToThe(tenPow), roundingMode);
6 return intVal;
7 }
見下面👇
1 // 情形二首先計算應該除以的10的次冪的指數 2 private static BigInteger bigTenToThe(int n) { 3 if (n < 0)// 指數小於0,這里的指數代表的是應該舍去的位數,一般的小於0並不會進入這個方法 4 return BigInteger.ZERO; 5 6 if (n < BIG_TEN_POWERS_TABLE_MAX) {// BIG_TEN_POWERS_TABLE_MAX為常量,等於19*16 7 BigInteger[] pows = BIG_TEN_POWERS_TABLE;// 將pows指向常量數組BIG_TEN_POWERS_TABLE,就是上面所說的那個指數與10的次冪映射的數組 8 if (n < pows.length)// 若n小於原生的數組的長度,說明10^n次冪包含在數組中直接返回就行 9 return pows[n]; 10 else // 否則將會拓展這個數組,原數組只能表示到10^18次方,這時候看上面的16*19這個表達式,這就看明白這個數組拓展是有極限的,極限就是最多拓展到16倍 11 // 下面這個方法就是拓展這個數組的,這樣做可能為了多次創建的時候可以這季節返回常量而不需要創建額外的BigInteger對象,例如最后一步 12 13 return expandBigIntegerTenPowers(n); 14 } 15 16 return BigInteger.TEN.pow(n);// 否則創建10^n次冪 17 }
1 // 情形一: 2 private static BigInteger divideAndRound(BigInteger bdividend, long ldivisor, int roundingMode) {// 解釋一下參數的單詞,代表啥自己猜 3 // bdividend:被除數 ldivisor:除數 quotient:商 4 boolean isRemainderZero; // record remainder is zero or not,記錄余數是否為0的標志位 5 int qsign; // quotient sign,商的符號 6 long r = 0; // store quotient & remainder in long 7 MutableBigInteger mq = null; // store quotient,使用MutableBigInteger保存商 8 // Descend into mutables for faster remainder checks 9 MutableBigInteger mdividend = new MutableBigInteger(bdividend.mag);// 根據被除數創建MutableBigInteger對象,這個才是真正的被除數 10 mq = new MutableBigInteger();// 初始化商,構建為MutableBigInteger對象 11 r = mdividend.divide(ldivisor, mq);// 使用被除數調用方法除以除數ldivisor,返回商mq與余數r 12 isRemainderZero = (r == 0);// 判斷余數是否為0 13 qsign = (ldivisor < 0) ? -bdividend.signum : bdividend.signum;// 商的符號將根據除數與被除數共同決定 14 if (!isRemainderZero) {// 若不能整除時, 15 if(needIncrement(ldivisor, roundingMode, qsign, mq, r)) {// needIncrement()方法根據RoundingMode是否需要增加商,若需要則商將增加ONE 16 mq.add(MutableBigInteger.ONE); 17 } 18 } 19 return mq.toBigInteger(qsign);// 根據商的符號qsign與MutableBigInteger類型的商mq創建BigInteger對象並返回 20 }