以前就有想法花一点时间学习一下Java中用于表示高精度运算的类BigDecimal
了解之后,总结一下以示学习
先看一组例子:
public static void main(String[] s) {
double a = 0.1, b = 0.2;
System.out.println(a + b);
}
输出结果:
0.30000000000000004
事与愿违,结果并不是我们想象的0.3;
原因很简单,计算机在做运算的时候,会先将十进制变成二进制,再对二进制进行运算;在十进制转换为二进制的时候,有时候不能使用有限的一个二进制来表达,尤其是遇到有小数的情况,即出现了精度丢失问题,进而导致运算的时候出现了事与愿违的一幕。如0.1转换为二进制是
0.000110011001100110011001100…………
既然有问题,那就必有解决办法;在Java中是使用BigDecimal 来解决;是java.math包中提供的API类,用来对超过16位有效位的数进行精确的运算。除了加减乘除运算,还提供了格式化,舍入等方法。
BigDecimal(int)
创建一个具有参数所指定整数值的对象
BigDecimal(double)
创建一个具有参数所指定双精度值的对象
BigDecimal(long)
创建一个具有参数所指定长整数值的对象
BigDecimal(String)
创建一个具有参数所指定以字符串表示的数值的对象
问题分析:
public static void main(String[] s) {
BigDecimal bigDecimal = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.2");
BigDecimal bigDecimal3 = new BigDecimal(0.1); // 传double构造方法
BigDecimal bigDecimal4 = new BigDecimal(0.2); // 传double构造方法
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal3.add(bigDecimal4));
}
输出结果:
0.3
0.3000000000000000166533453693773481063544750213623046875
原因:
- 使用BigDecimal类构造方法传入double类型时,计算的结果也是不精确的!因为不是所有的浮点数都能够被精确的表示成一个double
类型值,有些浮点数值不能够被精确的表示成 double 类型值,因此它会被表示成与它最接近的 double 类型的值。- String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,通常建议优先使用String构造方法。也是官方文档推荐的。
BigDecimal bigDecimal = new BigDecimal("0.1");
BigDecimal bigDecimal2 = new BigDecimal("0.2");
BigDecimal bigDecimal3 = new BigDecimal("-0.2");
// 加
System.out.println(bigDecimal.add(bigDecimal2));
// 乘
System.out.println(bigDecimal.multiply(bigDecimal2));
// 减
System.out.println(bigDecimal.subtract(bigDecimal2));
// 除
System.out.println(bigDecimal.divide(bigDecimal2));
// 绝对值
System.out.println(bigDecimal3.abs());
// 比较大小,-1小于,0等于,1大于
System.out.println(bigDecimal.compareTo(bigDecimal2));
运行结果:
0.3
0.02
-0.1
0.5
0.2
-1
运算的时候保留小数位,并指定某种具体的保留模式
/*
* 保留小数位(2位),以及舍入模式( RoundingMode.HALF_UP:四舍五入)
*/
BigDecimal bigDecimal = new BigDecimal("0.333");
BigDecimal bigDecimal2 = new BigDecimal("0.111");
// 加
System.out.println(bigDecimal.add(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 乘
System.out.println(bigDecimal.multiply(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 减
System.out.println(bigDecimal.subtract(bigDecimal2).setScale(2, RoundingMode.HALF_UP));
// 除(三分之一四舍五入模式保留二位小数)
System.out.println(bigDecimal2.divide(bigDecimal, 2, RoundingMode.HALF_UP));
运行结果:
0.44
0.04
0.22
0.33
注意:
- 值得一提的是,运用除法的时候,如果出现无限循环小数时,就会抛异常:
java.lang.ArithmeticException:Non-terminating decimal expansion; no exact representable decimal result.
解决异常的方法就是设置小数点位数- 加减乘和除设置保留小数方式不一样
- 其他的舍入模式可以参见jdk文档(RoundingMode类),这里不做介绍,传送门:http://www.matools.com/api/java8
由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制
// 建立货币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
BigDecimal a= new BigDecimal("15.483");
BigDecimal b = new BigDecimal("0.1235");
BigDecimal c= a.multiply(b);
System.out.println(currency.format(a));
System.out.println(percent.format(b));
System.out.println(currency.format(c));
System.out.println(percent.format(c));
运行结果:
¥15.48
12%
¥1.91
191%
注意:
- 选择系统默认的货币格式,且默认保留两位小数(钱就这样哈哈),采用四舍五入模式;
如需选择其他的货币格式,可选择性的传入Locale里面的常量,如
NumberFormat currency1 = NumberFormat.getCurrencyInstance(Locale.US) // $美元- 选择系统默认的百分比格式,且默认不保留小数位,采用四舍五入模式;
如需指定保留小数位,可使用setMaximumFractionDigits
和getMinimumFractionDigits
设置最大最小的小数位
// 建立货币格式化引用
NumberFormat currency = NumberFormat.getCurrencyInstance();
// 建立百分比格式化引用
NumberFormat percent = NumberFormat.getPercentInstance();
// 小数点最多三位,不够也不用补零
percent.setMaximumFractionDigits(3);
BigDecimal a = new BigDecimal("15.483");
BigDecimal b = new BigDecimal("0.1235");
BigDecimal c = a.multiply(b);
System.out.println(currency.format(a));
System.out.println(percent.format(b));
System.out.println(currency.format(c));
System.out.println(percent.format(c));
运行结果:
¥15.48
12.35%
¥1.91
191.215%
换成:
// 小数点最少三位,不够补零
percent.setMinimumFractionDigits(3);
运行结果:
¥15.48
12.350%
¥1.91
191.215%
提示:
- 也可以使用setMaximumIntegerDigits和setMinimumIntegerDigits设置最多和最少整数位,用法和小数位设置一致
除了NumberFormat,也可以使用它的子类DecimalFormat,对Decimal数值进行格式化(保留小数位)
/*
* 0:少了用0补,多了切掉,四舍五入
* #:少了也不补(就空着),多了切掉,四舍五入
* 注意:使用#时数值0不能表示一个位置
*/
DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(new BigDecimal("3.435")));
System.out.println(df.format(new BigDecimal(0)));
System.out.println(df.format(new BigDecimal("0.00")));
System.out.println(df.format(new BigDecimal("0.001")));
System.out.println(df.format(new BigDecimal("0.006")));
System.out.println(df.format(new BigDecimal("10.206")));
System.out.println("===========");
DecimalFormat df1 = new DecimalFormat("0.##");
System.out.println(df1.format(new BigDecimal("3.435")));
System.out.println(df1.format(new BigDecimal(0)));
System.out.println(df1.format(new BigDecimal("0.00")));
System.out.println(df1.format(new BigDecimal("0.001")));
System.out.println(df1.format(new BigDecimal("0.006")));
System.out.println(df1.format(new BigDecimal("10.206")));
运行结果:
3.44
0.00
0.00
0.00
0.01
10.21
=========
3.44
0
0
0
0.01
10.21
注意:
- 0和#都是占位作用,是最多位数的作用,区别在于不够时#不补,而0补零
- #后面不能有0,因为如果位数不够,#表示不补,0表示补0,这是矛盾的,会报异常
java.lang.IllegalArgumentException: Unexpected ‘0’ in pattern “0.#0”
来源于网络,我也在使用的一个BigDecimal工具类
import java.math.BigDecimal;
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
//默认除法保留小数位数
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
}