Java 精度处理
1、问题
public static void main(String[] args) {
System.out.println(0.2 + 0.1); //0.30000000000000004
System.out.println(0.3 - 0.1); //0.19999999999999998
System.out.println(0.2 * 0.1); //0.020000000000000004
System.out.println(0.3 / 0.1); //2.9999999999999996
}
上面这些代码直接使用加减乘除符号来计算,计算出的结果和我们预期的并不一样。要避免这些问题,我们就需要使用java.math.BigDecimal
。
2、BigDecimal
2.1 构造器
2.11 BigDecimal(int)
创建一个具有参数所指定整数值的对象。
2.12 BigDecimal(double)
创建一个具有参数所指定双精度值的对象。 不推荐使用。
这里为什么不推荐使用呢,请看下面的例子。
public static void main(String[] args) {
Double num=0.3;
System.out.println(new BigDecimal(num));//0.299999999999999988897769753748434595763683319091796875
System.out.println(BigDecimal.valueOf(num));//0.3
System.out.println(new BigDecimal(String.valueOf(num)));//0.3
}
可以看到如果使用double
参数的构造器,那么还是存在精度问题,推荐使用的是String
参数的构造器。当然也可以BigDecimal
的静态方法valueOf
。
2.13 BigDecimal(long)
创建一个具有参数所指定长整数值的对象。
2.14 BigDecimal(String)
创建一个具有参数所指定以字符串表示的数值的对象。推荐使用
2.2 方法
-
add(BigDecimal)
BigDecimal
对象中的值相加,然后返回这个对象。 -
subtract(BigDecimal)
BigDecimal
对象中的值相减,然后返回这个对象。 -
multiply(BigDecimal)
BigDecimal
对象中的值相乘,然后返回这个对象。 -
divide(BigDecimal)
BigDecimal
对象中的值相除,然后返回这个对象。 -
toString()
将
BigDecimal
对象的数值转换成字符串。 -
doubleValue()
将
BigDecimal
对象中的值以双精度数返回。 -
floatValue()
将
BigDecimal
对象中的值以单精度数返回。 -
longValue()
将
BigDecimal
对象中的值以长整数返回。 -
intValue()
将
BigDecimal
对象中的值以整数返回。
2.3 舍入模式
2.3.1 ROUND_CEILING
向正无穷方向舍入
2.3.2 ROUND_DOWN
向零方向舍入
2.3.3 ROUND_FLOOR
向负无穷方向舍入
2.3.4 ROUND_HALF_DOWN
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5
2.3.5 ROUND_HALF_EVEN
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN
2.3.6 ROUND_HALF_UP
向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6,也就是我们常说的四舍五入
2.3.7 ROUND_UNNECESSARY
计算结果是精确的,不需要舍入模式
2.3.8 ROUND_UP
向远离0的方向舍入
2.4 方法使用
基本的加减乘除如下:
public static void main(String[] args) {
BigDecimal a=BigDecimal.valueOf(12);
BigDecimal b=BigDecimal.valueOf(3);
System.out.println("a + b = "+a.add(b));
System.out.println("a - b = "+a.subtract(b));
System.out.println("a * b = "+a.multiply(b));
System.out.println("a / b = "+a.divide(b));
}
在除法运算的时候,如果被除数不能被除数整除,会抛出java.lang.ArithmeticException
异常。所以在做除法运算的时候需要多传入两个参数:保留小数位数、舍入模式。如下演示:
public static void main(String[] args) {
BigDecimal a=BigDecimal.valueOf(10);
BigDecimal b=BigDecimal.valueOf(3);
System.out.println("a / b = "+a.divide(b,2,ROUND_HALF_UP));
}
上图中演示的舍入模式为我们常用的四舍五入模式。
3、BigDecimal
格式化
BigDecimal
格式化经常用来格式化金额,使用到的是java.text.DecimalFormat
格式化器。
public static void main(String[] args) {
BigDecimal money=BigDecimal.valueOf(361254624.33);
System.out.println(new DecimalFormat("###,###,###.##").format(money));
//运行结果:361,254,624.33
}
java.text.DecimalFormat
是线程不安全的,这一点要注意。
有下面几种解决思路:
-
为
DateFormat
对象加锁public class DecimalFormatDemo { private static final DecimalFormat decimalFormat=new DecimalFormat("###,###,###.##"); public static String formatMoney(BigDecimal money){ synchronized(decimalFormat){ return decimalFormat.format(money); } } public static void main(String[] args) { System.out.println(formatMoney(BigDecimal.valueOf(101211111.11))); } }
这种方式使得一次只能让一个线程访问
DecimalFormat
对象,而其他线程只能等待。 -
使用
ThreadLocal
public class DecimalFormatDemo { private static final ThreadLocal<DecimalFormat> decimalFormat = new ThreadLocal<DecimalFormat>() { @Override protected DecimalFormat initialValue() { return new DecimalFormat("###,###,###.##"); } }; public static String formatMoney(BigDecimal money) { return decimalFormat.get().format(money); } public static void main(String[] args) { System.out.println(formatMoney(BigDecimal.valueOf(101211111.11))); } }
这种方式使用
ThreadLocal
变量去容纳DateFormat
对象,也就是说每个线程都有一个属于自己的副本,并无需等待其他线程去释放它。这种方法会比使用同步块更高效。 -
使用临时变量,就是每次都去new
public class DecimalFormatDemo { public static String formatMoney(BigDecimal money) { return new DecimalFormat("###,###,###.##").format(money); } public static void main(String[] args) { System.out.println(formatMoney(BigDecimal.valueOf(101211111.11))); } } return new DecimalFormat("###,###,###.##").format(money); } public static void main(String[] args) { System.out.println(formatMoney(BigDecimal.valueOf(101211111.11))); } }