Java 精度处理

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)));
        }
    }
    
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值