BigDecimal使用
介绍
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。
为啥使用BigDecimal
小数转二进制很大的可能会出现无限循环或者转成二进制之后数据特别长,而计算机中对于小数的存储按照IEEE754标准只会保留一定的二进制位数,按精度不同,保留的位数不同。按照保留的二进制位数 float型变量仅能接收实数型常量的6-7位有效数字。 double型变量能接收实数型常量的15-16位有效数字。 这样的精度对于部分场合是不适合的,所以我们使用BigDecimal.
/*
* 精度损失问题,3490593 转二进制为 1101010100001100100001 0.9 转二进制位 1110.....省略,最终得到1101010100001100100001.1110
* 舍去到21位的时候,最后面的1进行进1处理,所以得到尾数位为101010000110010001000,最终输出为3490594.0
* */
System.out.println(3490593.9f);
// 输出为 3490594.0
/**
* 0.6 转二进制,是1001100110011001100110011.......,一直循环,用单精度表示 0 01111110 00110011001100110011010
* 尾数00110011001100110011010,是1001100110011001100110011 首位1作为隐藏位后,第24位做了进1处理。
* 所以用浮点数存放的0.6比 0.6要大。用1.0 - 0.6f 得到的数据比0.4要小
* 至于为啥0.6f 直接输出还是0.6,这个java这边对float输出是是做了转换的,(具体我也不清楚,c++源码了)
* */
System.out.println(Integer.toBinaryString(Float.floatToIntBits(0.6f)));
System.out.println(1.0f-0.6f);
System.out.println(0.6f);
// 输出为
111111000110011001100110011010
0.39999998
0.6
简而言之,你将一个数定义为float和double的时候,就已经产生了精度损失。
int和float计算问题
// int类型计算会对结果进行截取,小数点后就不要了
System.out.println(3/4);// 输出 0
// 所以一般进行科学计算会使用float和double类型,
/**
* 下面程序说明了 float 有6-7位有效数字,double有 15-16位有效数字,
* 有效数字不包含最后一位,原因是小数转浮点数,尾数只保存一定的位数,超过后进行舍去或进1
* 有效数字不包含最前面的0,原因是小数转浮点数,如果前面都是0,尾数左移一位,阶码 -1。。。
* */
System.out.println(1/3.0f);
System.out.println(1/30.0f);
System.out.println(1/300.0f);
System.out.println(1/0.3f);
System.out.println(1/3.0);
System.out.println(1/30.0);
System.out.println(1/300.0);
System.out.println(1/0.3);
// 输出
0.33333334
0.033333335
0.0033333334
3.3333333
0.3333333333333333
0.03333333333333333
0.0033333333333333335
3.3333333333333335
/**
* 浮点型转字符串按如下方法,这里要说明的是按照这种方法保留一定小数位是按四舍五入的规则来的
* 还有你可以通过float和double的包装类将数字型字符串转成浮点型
* */
String d1 = String.format("%.2f", 0.335);
String d2 = String.format("%.2f", 0.334);
System.out.println(d1);
System.out.println(d2);
Float f1 = Float.valueOf(d1);
Float f2 = Float.valueOf(d2);
System.out.println(f1);
System.out.println(f2);
BigDecimal计算
BigDecimal和浮点型互转
/**
* 将浮点型转BigDecimal
* */
float f = 0.6f;
BigDecimal a = new BigDecimal("0.6");
// 这个方法内部是这么操作的 new BigDecimal(Double.toString(val));
BigDecimal b = BigDecimal.valueOf(f);
// 用下面的方法获取的BigDecimal对象,存放的数字是不准确的,不建议使用,你可以理解一个数定义为浮点数就产生了精度损失,变的不准确,你在构建BigDecimal时给的就不是一个准确的数字
BigDecimal c = new BigDecimal(0.6);
System.out.println(a);
System.out.println(c);
// 输出
0.6
0.59999999999999997779553950749686919152736663818359375
/**
* 将BigDecimal转浮点数,或者你只需要数字的整数部分
* */
double v = a.doubleValue();
int i = a.intValue();
System.out.println(v);
System.out.println(i);
// 输出
0.6
0
BigDecimal运算
/**
* BigDecimal的加减乘除,计算一般需要制定保留多少位小数,特别是除法,否则遇到无限循环小数,会抛出异常
* java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
* BigDecimal的加减乘除方法有很多的重载方法,此处介绍比较常用的方法
* */
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.3");
BigDecimal c = new BigDecimal("-0.3");
// BigDecimal divide = a.divide(b); --比如这样就会抛出异常
// 除法, a/b -- b为被数,2为保留的小数位,RoundingMode.HALF_UP表示进行四舍五入操作
BigDecimal divide = a.divide(b, 2, RoundingMode.HALF_UP); // 0.33
// 乘法
BigDecimal multiply = a.multiply(b); // 0.03
// 加法
BigDecimal add = a.add(b); // 0.4
// 减法
BigDecimal subtract = a.subtract(b); // -0.2
// 求绝对值
BigDecimal abs = c.abs(); // 0.1
//求大小
BigDecimal max = a.max(b); // 0.3
BigDecimal min = a.min(b); // 0.1
// 求商
BigDecimal bigDecimal = a.divideToIntegralValue(b); //0
// 求余
BigDecimal remainder = a.remainder(b); //0.1
// 返回商和余数的数组
BigDecimal[] bigDecimals = a.divideAndRemainder(b); // [0, 0.1]
// 返回当前BigDecimal对象
BigDecimal plus = a.plus(); // 0.1
// 返回当前数的负数
BigDecimal negate = a.negate(); // -0.1
// a的幂次方
BigDecimal pow = a.pow(2); // 0.01
// 返回a的精度
int precision = a.precision(); // 1
// 小数点左移 1位,右移的方法不介绍
BigDecimal movePointLeft = a.movePointLeft(1); // 0.01
//https://blog.csdn.net/cumubi7453/article/details/107798930 大概就是-132.45 --》 -12345 0.0 --》 0 0.12 --> 12
BigInteger bigInteger = a.unscaledValue(); // 1
BigDecimal保留小数位数和舍入模式
/**
* 上面的运算除了除法, 其他的都没有指定舍入模式,和保留位数,此处介绍设置保留位数和舍入模式的方法
* */
BigDecimal b = new BigDecimal("0.3333333333");
// 保留两位,四舍五入
BigDecimal bigDecimal = b.setScale(2,RoundingMode.HALF_UP);
舍入模式RoundingMode
// RoundingMode 枚举类存放了BigDecimal的舍入模式, 进入其中,可以通过注释了解舍入模式的详情
UP(BigDecimal.ROUND_UP) //舍入远离零的舍入模式
DOWN(BigDecimal.ROUND_DOWN) //接近零的舍入模式。
CEILING(BigDecimal.ROUND_CEILING) //接近正无穷大的舍入模式
FLOOR(BigDecimal.ROUND_FLOOR) // 接近负无穷大的舍入模式
HALF_UP(BigDecimal.ROUND_HALF_UP) //向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式 (四舍五入)
HALF_DOWN(BigDecimal.ROUND_HALF_DOWN) // 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式(五舍六入)
HALF_EVEN(BigDecimal.ROUND_HALF_EVEN) //向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入UNNECESSARY(BigDecimal.ROUND_UNNECESSARY) //断言请求的操作具有精确的结果,因此不需要舍入,如果不成功,抛出异常 比如0.333保留2位,抛异常
MathContext入参
/**
* BigDecimal的部分方法中有MathContext对象作为参数传入
* MathContext对象用于指定计算后数据的保留的有效数字Precision,这里的有效数字和保留小数点后多少位是有区别的,可以通过下面的示例看出区别
* 还可以指定数据的舍入模式
* */
BigDecimal b = new BigDecimal("0.00333333333");
MathContext mathContext = new MathContext(2, RoundingMode.HALF_UP);
//和上面的的效果等同,不设置舍入模式,默认舍入模式为RoundingMode.HALF_UP
MathContext mathContext1 = new MathContext(2);
BigDecimal bigDecimal = b.setScale(2,RoundingMode.HALF_UP); //0.00
BigDecimal plus = b.plus(mathContext1);//0.0033