BigDecimal源码

  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     }

 

转载于:https://www.cnblogs.com/flydoging/p/10428247.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值