【刨根问底】BigDecimal 案例和部分源码分析
作者:互联网
本文总以下几个部分:
前言
Bigdecimal定义
Bigdecimal创建方式
Bigdecimal部分源码分析
Bigdecimal坑
Bigdecimal使用建议
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
完整版源码解析,感兴趣的自行去看。
标签:BigDecimal,构造方法,int,double,源码,刨根问底,offset,public 来源: https://blog.51cto.com/10983206/2563639