JDK8 BigDecimal API-创建BigDecimal源码浅析二
第二篇,慢慢来
根据指数调整有效小数位数
1 // 上一篇由字符串创建BigDecimal代码中,有部分代码没有给出,这次补上 2 // 这个是当解析字符数组时存在有效指数时调整有小小数位数方法 3 private int adjustScale(int scl, long exp) {// 参数:scl-->>原生有效小数位数,exp:有效指数 4 long adjustedScale = scl - exp;// 这个其实自己写个例子就能明白,例如数值1.234*10^-3次方,此时实际数值位0.001234scl=3,exp=-3, 5 // 实际有效小数位数是不是3-(-3)=6 6 if (adjustedScale > Integer.MAX_VALUE || adjustedScale < Integer.MIN_VALUE)// 前提是这个scl-exp还在int的表数范围,否则抛出异常 7 throw new NumberFormatException("Scale out of range."); 8 scl = (int) adjustedScale;// 若还在int的表数范围则强转为int并返回 9 return scl; 10 }
解析字符数组中的指数表达式
1 // 解析指数表达式 2 private static long parseExp(char[] in, int offset, int len){ 3 long exp = 0;// 先定义一个指数值exp 4 offset++;// 将索引自增,在调用该方法时,索引尚还在指数标识符'e'/'E'这里,索引需要自增,不清楚的看上一篇文章 5 char c = in[offset];// 获取当前字符c,并将需要解析的字符长度自减 6 len--; 7 boolean negexp = (c == '-');// 判断指数表达式的首位是不是符号位,并以negexp标识位表示是否为负 8 // optional sign 9 if (negexp || c == '+') {// 首位就是符号位,不是'-'就是'+' 10 offset++;// 解析出符号位,索引自增 11 c = in[offset];// 继续获取当前字符c,长度自减 12 len--; 13 } 14 if (len <= 0) // no exponent digits,若第一位不是符号位,但是长度已经小于等于0,就代表该指数表达式为空将抛出异常 15 throw new NumberFormatException(); 16 // skip leading zeros in the exponent 17 while (len > 10 && (c=='0' || (Character.digit(c, 10) == 0))) {// 去除除符号位外的前置字符'0',因为并没有什么卵用 18 offset++; 19 c = in[offset]; 20 len--; 21 } 22 if (len > 10) // too many nonzero exponent digits,若除'0'后指数位数还是大于10则抛出异常,一般指数都很少, 23 //10^100:指数才3位,但是这个数量级可是比整个宇宙的行星数量还多 24 throw new NumberFormatException(); 25 // c now holds first digit of exponent 26 for (;; len--) {// 需要解析的字符长度小于等于10进入循环 27 int v;// 定义当前循环中解析字符c的实际数值 28 if (c >= '0' && c <= '9') {// 若字符c是数字值,此时获取c的实际数字值并赋值给v 29 v = c - '0'; 30 } else { 31 v = Character.digit(c, 10); 32 if (v < 0) // not a digit 33 throw new NumberFormatException(); 34 } 35 exp = exp * 10 + v;// 计算当前已经解析出的指数表达式的值,因多解析出一位因此整个表达式的值:原指数值exp上升一个进位制需要扩大10倍 36 if (len == 1)// 若此时需要解析的长度已经是1则结束循环(此时当前字符的的位置尚未自减) 37 break; // that was final character 38 offset++;// 若还未解析到最后的字符则长度自减,并获取下一个字符并赋值给c 39 c = in[offset]; 40 } 41 if (negexp) // apply sign,解析完成以后根据符号位negexp获取指数的实际值并返回 42 exp = -exp; 43 return exp; 44 }
BigDecimal的构造方法,这些构造包含推荐使用的以String为构造参数的方法最终调用的都是上篇文章所分析的以字符数组为参数的构造器
// 1 public BigDecimal(char[] in) { this(in, 0, in.length); } // 2 public BigDecimal(char[] in, MathContext mc) { this(in, 0, in.length, mc); } // 3 public BigDecimal(String val) { this(val.toCharArray(), 0, val.length()); } // 4 public BigDecimal(String val, MathContext mc) { this(val.toCharArray(), 0, val.length(), mc); }
接下来看一下其它构造器,这些其实都不是推荐使用的构造器
public BigDecimal(double val) {// 这个方法并不推荐使用,因为浮点数在计算机中听不一定能准确表示,因此使用该构造方法创建的实际值可能与传入参数的看到的表面值并不相同 this(val,MathContext.UNLIMITED);// 方法转发,调用下面的方法 } public BigDecimal(double val, MathContext mc) { if (Double.isInfinite(val) || Double.isNaN(val))// 传入的参数为无穷大或者为非数,则抛出异常 throw new NumberFormatException("Infinite or NaN"); // Translate the double into sign, exponent and significand, according // to the formulae in JLS, Section 20.10.22. long valBits = Double.doubleToLongBits(val);// 该方法还没细看,作用就是在存储层面将double转变为long类型,double与long类型都是64位,但是double首位为符号位,
接下来的11位为指数位,最后的52位为尾数位.而long类型64位都是尾数.这个方法就是将double不按照原有的解析逻辑解析,
而是直接按照long类型解析
int sign = ((valBits >> 63) == 0 ? 1 : -1);// 判断最高位符号位的正负对sign进行赋值 int exponent = (int) ((valBits >> 52) & 0x7ffL);// 0x7ffL的二进制表示:低位11位均为1其余都是0,这一步就是获取double双精度浮点数的中间11位的指数部分 long significand = (exponent == 0 ? (valBits & ((1L << 52) - 1)) << 1 : (valBits & ((1L << 52) - 1)) | (1L << 52));// valBits & ((1L << 52) - 1):用于获取double的尾数位 exponent -= 1075;// 自减1075,因为在原始的double指数位中存储的是指数实际值+1023,至于为什么可以取看看浮点数的底层原理,但是这里不仅减去了1023
// 又减去了52,这是因为double的实际值=((-1)^sign)*significand*2^exponent,而标准的表示中significand前默认是"1."
// 即significand全部都是小数,这也是为啥在指数不为0时进行"| (1L << 52)"操作.此时significand为long类型的整数也就是
// 上升了52个进位制,因此需要减去52,索引一共减去1075
// At this point, val == sign * significand * 2**exponent. /* * Special case zero to supress nonterminating normalization and bogus * scale calculation. */ if (significand == 0) {// 若处理完成后的尾数为0,这是特殊情况:此时BigDecimal值为0,有效位数为1 this.intVal = BigInteger.ZERO; this.scale = 0; this.intCompact = 0; this.precision = 1; return; } // Normalize while ((significand & 1) == 0) { // i.e., significand is even,去除尾数右侧的0 significand >>= 1;// 若尾数为偶数则缩小二倍并将指数自增(实际值还不变) exponent++; } int scale = 0; // Calculate intVal and scale BigInteger intVal;// 定义有效小数位数与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);// 根据double的实际值=((-1)^sign)*significand*2^exponent
// 获取实际值
} compactVal = compactValFor(intVal);// 之前分析过就是根据intVal获取简洁值(前提是intVal对象的值可以使用long类型表示而不溢出) } int prec = 0;int mcp = mc.precision;// 定义有效位数及获取MathContext中的有效位数 if (mcp > 0) { // do rounding int mode = mc.roundingMode.oldMode; int drop; if (compactVal == INFLATED) {// 代表intVal对象的实际值已经溢出 prec = bigDigitLength(intVal);// 见👇 drop = prec - mcp;// 获取需要舍去的位数,若大于0则进入循环进行舍去.舍去的操作见上一篇文章 while (drop > 0) { scale = checkScaleNonZero((long) scale - drop); intVal = divideAndRoundByTenPow(intVal, drop, mode); compactVal = compactValFor(intVal); if (compactVal != INFLATED) { break; } prec = bigDigitLength(intVal); drop = prec - mcp; } } if (compactVal != INFLATED) { prec = longDigitLength(compactVal); drop = prec - mcp; while (drop > 0) { scale = checkScaleNonZero((long) scale - drop); compactVal = divideAndRound(compactVal, LONG_TEN_POWERS_TABLE[drop], mc.roundingMode.oldMode); prec = longDigitLength(compactVal); drop = prec - mcp; } intVal = null; } } this.intVal = intVal; this.intCompact = compactVal; this.scale = scale; this.precision = prec;// 最后进行赋值操作 }
计算BigInteger的十进制位数
private static int bigDigitLength(BigInteger b) { /* * Same idea as the long version, but we need a better * approximation of log10(2). Using 646456993/2^31 * is accurate up to max possible reported bitLength. */ if (b.signum == 0)// 表明该BigInteger数值为0,则数值位数就是1 return 1; int r = (int)((((long)b.bitLength() + 1) * 646456993) >>> 31);// bitLength()方法获取BigInteger对象所表示数值的二进制位数,该方法还是使用
// lg(X) = (1+log2(X))*lg2 这个公式,最终计算得出b在十进制下的位数r
return b.compareMagnitude(bigTenToThe(r)) < 0? r : r+1;// 最终比较b的数值与10^r的大小,若小于则返回r否则需要增加一个量级 }