本文总以下几个部分:
- 前言
- 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的。将就一下吧。看源码有的时候不是真的把他的设计记住,主要是能学到一些别人的思路。
标签:BigDecimal,int,double,源码,b1,刨根问底,offset,public From: https://blog.51cto.com/u_11702014/6443827