【刨根问底】BigDecimal 案例和部分源码分析

本文总以下几个部分:

  1. 前言

  2. Bigdecimal定义

  3. Bigdecimal创建方式

  4. Bigdecimal部分源码分析

  5. Bigdecimal坑

  6. Bigdecimal使用建议

  7. Bigdecimal工具类


前言

在咱们开发过程中很容易遇到计算的问题,普通计算其实也还好使用int、long、double、float基本上能应付。但是如果涉及到数据类型转后在处理等就不是很好做,于是这会Bigdecimal就出现了。


BigDecimal定义

不变的,任意精度的带符号的十进制数字。A BigDecimal由任意精度整数未缩放	值和32位整数级别组成 。如果为零或正数,则刻度是小数点右侧的位数。如果	是负数,则数字的非标定值乘以10,以达到等级的否定的幂。因此,BigDecimal	所代表的BigDecimal值为(unscaledValue × 10-scale) 。	BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换


BigDecimal的创建方式

/**	 * @author Java后端技术栈	 * @date 2019/7/6	 */	public class BigDecimalDemo {	public static void main(String[] args) {	//new 形式创建	BigDecimal bigDecimal = new BigDecimal("1");	System.out.println(bigDecimal);	//valueOf形式创建	BigDecimal b1 = BigDecimal.valueOf(2.3333);	System.out.println(b1);	//BigDecimal.形式创建	BigDecimal b2 = BigDecimal.ZERO;	System.out.println(b2);	}	}


部分源码分析

以下JDK版本为:1.8

常用几个重要的属性

// 若BigDecimal的绝对值小于Long.MAX_VALUE,放在这个变量中	//public static final long MIN_VALUE = 0x8000000000000000L;	private final transient long intCompact;	//BigDecimal的标度(小数点),	//输入数除以10的scale次幂(32 位的整数标度)	private final int scale;	// BigDecimal的未scale的值,BigInteger是	// 一个任意长度的整数(整数非标度值)	private final BigInteger intVal;	// BigDecimal的精度(精度是非标度值的数字个数)	private transient int precision;	//toString后缓存	private transient String stringCache;

BigDecimal是没有无参构造方法的,所以这里从上面案例中的第一种创建方式使用的构造方法开始:

public BigDecimal(String val) {	//字符串转换成char数组,offset设置为0	this(val.toCharArray(), 0, val.length());	}

看得出来,这里没做什么,然后直接调用了重载的构造方法:

public BigDecimal(char[] in, int offset, int len) {	    //MathContext.UNLIMITED这个在老版本中有使用,新版本没有使用了	    this(in,offset,len,MathContext.UNLIMITED);	}

继续调用重载的方法:

/**	     * 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 	     *  BigDecimal(String) 构造方法相同的字符序列,同时允许指定子数组。 	     * 注意,如果字符数组中已经提供字符的序列,则使用此构造方法要比将	     * char 数组转换为字符串并使用 BigDecimal(String) 构造方法更快。 	     * @param in  作为源字符的 char 数组	     * @param offset  要检查的数组中的第一个字符	     * @param len  要考虑的字符数	     * @param mc  没有使用	     */	 public BigDecimal(char[] in, int offset, int len, MathContext mc) {	// 防止长度过大。	if (offset+len > in.length || offset < 0)	throw new NumberFormatException();	      /*	       * 这是BigDecimal构造函数的主字符串;所有传入字符串都在这里结束;	       * 它使用显式(内联)解析来提高速度,并为非紧凑情况生成最多	       * 一个中间(临时)对象(char[]数组)。	       */	// 对所有字段值使用局部变量,直到完成	int prec = 0;   // BigDecimal的数字的长度	int scl = 0;   // BigDecimal的标度	long rs = 0;   // intCompact值	BigInteger rb = null; // BigInteger的值		// 使用数组边界检查来处理太长、len == 0、错误偏移等等。	try {	            //符号的处理	            boolean isneg = false; // '+'为false,'-'为true	if (in[offset] == '-') {// 第一个字符为'-'	isneg = true;	offset++;	len--;	} else if (in[offset] == '+') {// 第一个字符为'+'	offset++;	len--;	}		            //数字有效部分	boolean dot = false;//当有“.”时为真。	            int cfirst = offset; //记录integer的起始点	            long exp = 0;  //exponent	            char c; //当前字符		boolean isCompact = (len <= MAX_COMPACT_DIGITS);	// 大于18位是BigInteger,创建数组	            char coeff[] = isCompact ? null : new char[len];	int idx = 0;		for (; len > 0; offset++, len--) {	c = in[offset];	// 有数字,确定c(Unicode 代码点)是否为数字	if ((c >= '0' && c <= '9') || Character.isDigit(c)) {	// 第一个紧化情况,我们不需要保留字符我们可以就地计算值。	if (isCompact) { // 非BigInteger数值	// 获取使用10进制的字符 c 的数值	int digit = Character.digit(c, 10);	if (digit == 0) {// 为 0	if (prec == 0)	prec = 1;	else if (rs != 0) {	rs *= 10;	++prec;	}// 否则,数字为冗余前导零	} else { // 非0	if (prec != 1 || rs != 0)	++prec; // 如果前面加0,则prec不变	rs = rs * 10 + digit;	}	} else {// the unscaled value可能是一个BigInteger对象。	if (c == '0' || Character.digit(c, 10) == 0) {// 为0	if (prec == 0) {	coeff[idx] = c;	prec = 1;	} else if (idx != 0) {	coeff[idx++] = c;	++prec;	} // 否则c一定是多余的前导零	} else {	if (prec != 1 || idx != 0)	++prec; // 如果前面加0,则prec不变	coeff[idx++] = c;	}	}	if (dot)// 如果有小数点	++scl;	continue;	}	// 当前字符等于小数点	if (c == '.') {	// have dot	if (dot)// 存在两个小数点	throw new NumberFormatException();	dot = true;	continue;	}	// exponent 预期	if ((c != 'e') && (c != 'E'))	throw new NumberFormatException();	offset++;	c = in[offset];	len--;	boolean negexp = (c == '-'); // 当前字符是否为'-'	if (negexp || c == '+') { // 为符号	offset++;	c = in[offset];	len--;	}	if (len <= 0)// 没有 exponent 数字	throw new NumberFormatException();	// 跳过exponent中的前导零 	while (len > 10 && Character.digit(c, 10) == 0) {	offset++;	c = in[offset];	len--;	}	if (len > 10) // 太多非零 exponent 数字	throw new NumberFormatException();	// c 现在是 exponent的第一个数字	for (;; len--) {	int v;	if (c >= '0' && c <= '9') {	v = c - '0';	} else {	v = Character.digit(c, 10);	if (v < 0) // 非数字	throw new NumberFormatException();	}	exp = exp * 10 + v;	if (len == 1)	break;   // 最终字符	offset++;	c = in[offset];	}	if (negexp)                 // 当前字符为'-',取相反数	exp = -exp;	// 下一个测试需要向后兼容性	if ((int)exp != exp)         // 溢出	throw new NumberFormatException();	break;	}	// 这里没有字符了	if (prec == 0)              // 没有发现数字	throw new NumberFormatException();	// 如果exp不为零,调整标度。	if (exp != 0) {                 // 有显著的exponent	// 不能调用基于正确的字段值的checkScale	long adjustedScale = scl - exp;	if (adjustedScale > Integer.MAX_VALUE ||	adjustedScale < Integer.MIN_VALUE)	throw new NumberFormatException("Scale out of range.");	scl = (int)adjustedScale;	}	// 从precision中删除前导零(数字计数)	if (isCompact) {	rs = isneg ? -rs : rs;	} else {	char quick[];	if (!isneg) {	quick = (coeff.length != prec) ?	Arrays.copyOf(coeff, prec) : coeff;	} else {	quick = new char[prec + 1];	quick[0] = '-';	System.arraycopy(coeff, 0, quick, 1, prec);	}	rb = new BigInteger(quick);	// 获取rb(BigInteger)的compact值。	rs = compactValFor(rb);	}	} catch (ArrayIndexOutOfBoundsException e) {	throw new NumberFormatException();	} catch (NegativeArraySizeException e) {	throw new NumberFormatException();	}	this.scale = scl;	this.precision = prec;	this.intCompact = rs;	this.intVal = (rs != INFLATED) ? null : rb;	}


BigDecimal坑

案例

public class BigDecimalDemo {	public static void main(String[] args) {	double d = 2.33;	BigDecimal dd = new BigDecimal(d);	if (d == dd.doubleValue()) {	System.out.println(dd);	}	    }	 }

debug模式截图:



使用建议

  •  double 参数的构造方法,不允许使用!!!!因为它不能精确的得到相应的值;

  • String 构造方法是完全可预知的: 写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的0.1; 因此,通常建议优先使用 String 构造方法;

  • 静态方法 valueOf(double val) 内部实现,仍是将 double 类型转为 String 类型; 这通常是将 double(或float)转化为 BigDecimal 的首选方法;


工具类

import java.math.BigDecimal;		/**	 * 使用BigDecimal来计算	 *	 * @author Java后端技术栈	 * @date 2019/7/6	 */	public class BigDecimalUtil {	// 除法运算默认精度	private static final int DEF_DIV_SCALE = 10;		private BigDecimalUtil() {	}		/**	     * 精确加法	     */	public static double add(double value1, double value2) {	BigDecimal b1 = BigDecimal.valueOf(value1);	BigDecimal b2 = BigDecimal.valueOf(value2);	return b1.add(b2).doubleValue();	}		/**	     * 精确减法	     */	public static double sub(double value1, double value2) {	BigDecimal b1 = BigDecimal.valueOf(value1);	BigDecimal b2 = BigDecimal.valueOf(value2);	return b1.subtract(b2).doubleValue();	}		/**	     * 精确乘法	     */	public static double mul(double value1, double value2) {	BigDecimal b1 = BigDecimal.valueOf(value1);	BigDecimal b2 = BigDecimal.valueOf(value2);	return b1.multiply(b2).doubleValue();	}		/**	     * 精确除法 使用默认精度	     */	public static double div(double value1, double value2) throws IllegalAccessException {	return div(value1, value2, DEF_DIV_SCALE);	}		/**	     * 精确除法	     *	     * @param scale 精度	     */	public static double div(double value1, double value2, int scale) throws IllegalAccessException {	if (scale < 0) {	throw new IllegalAccessException("精确度不能小于0");	}	BigDecimal b1 = BigDecimal.valueOf(value1);	BigDecimal b2 = BigDecimal.valueOf(value2);	// return b1.divide(b2, scale).doubleValue();	return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();	}		/**	     * 四舍五入	     *	     * @param scale 小数点后保留几位	     */	public static double round(double v, int scale) throws IllegalAccessException {	return div(v, 1, scale);	}		/**	     * 比较大小	     * 相等返回true	     */	public static boolean equalTo(BigDecimal b1, BigDecimal b2) {	return b1 != null && b2 != null && 0 == b1.compareTo(b2);	}	}

完全把BigDecimal源码解析完成,至少要写成五篇文章,因为公众号每篇文字数不能超过五千。,所以在此分享一个源码阅读完整版的地址,但是JDK版本是1.6的。将就一下吧。看源码有的时候不是真的把他的设计记住,主要是能学到一些别人的思路。

参考:https://blog.csdn.net/en_joker/article/details/86589691

完整版源码解析,感兴趣的自行去看。


猜你喜欢

转载自blog.51cto.com/10983206/2563639