java中数字基本运算、金额运算精度问题小结

一、前言
  在我们日常工作中,经常会有涉及到数字的运算,其中金额的运算尤其重要且敏感,因为金额的运算若不注意处理的话,很容易因为精度的丢失,从而导致最终数据的异常,造成严重的系统错误。本文将对java中金额的运算处理进行简单小结。
  
二、NumberFormat类、DecimalFormat类、BigDecimal类简介
1、NumberFormat类
  NumberFormat类是所有数值格式的抽象基类,它继承了Format抽象类。NumberFormat类提供了格式化和分析数值的接口,还提供了一些方法来确定哪些语言环境具有数值格式,以及它们的名称是什么。其常用的方法说明如下:

//返回当前缺省语言环境的缺省数值格式
public final static NumberFormat getInstance();
//返回当前缺省语言环境的通用格式
public final static NumberFormat getCurrencyInstance();
//返回当前缺省语言环境的通用数值格式
public final static NumberFormat getNumberInstance();
//返回当前缺省语言环境的百分比格式
public final static NumberFormat getPercentInstance();  

另外,这几个方法都有相应的通过Locale类指定当前环境的方法。
例1:

//输出格式化后的数字
public class NumberTest {
    public static void main(String[] args) {
      double a = 12345.123456;
      double b = 0.123456;
      double c = 12345.67896789;
      double d = 0.125555;
      String s1 = NumberFormat.getInstance().format(a);  
      String s2 = NumberFormat.getCurrencyInstance().format(a);   
      String s3 = NumberFormat.getNumberInstance().format(a);   
      String s4 = NumberFormat.getPercentInstance().format(b); 
      String s5 = NumberFormat.getInstance().format(c);  
      String s6 = NumberFormat.getPercentInstance().format(d);  
      System.out.println("s1->" + s1);  
      System.out.println("s2->" + s2);
      System.out.println("s3->" + s3);
      System.out.println("s4->" + s4);
      System.out.println("s5->" + s5);
      System.out.println("s6->" + s6);
    }
}

输出

s1->12,345.123
s2->12,345.12
s3->12,345.123
s4->12%
s5->12,345.679
s6->13%

注意:格式化后,保留位数小数位后是四舍五入的。
  当然,如果说你想对某一个数字精确保留指定位数的话,可以通过相关参数来设置。
例2:

public class NumberTest {
    public static void main(String[] args) {
        double a = 12345.6789;
        double b = 1.2;
        NumberFormat format = NumberFormat.getInstance();  
        //设置数值的整数部分允许的最大位数
        format.setMaximumIntegerDigits(3);
        //设置数值的整数部分允许的最小位数
        format.setMinimumIntegerDigits(3);  
        // 设置数值的小数部分允许的最大位数
        format.setMaximumFractionDigits(3);
        //设置数值的小数部分允许的最小位数
        format.setMinimumFractionDigits(3);
        System.out.println("a->" + format.format(a)); 
        System.out.println("b->" + format.format(b)); 
    }
}

输出

a->345.679
b->001.200

2、DecimalFormat类
  DecimalFormat类是NumberFormat的一个具体子类,用于格式化十进制数字,通常用于涉及高精度的运算。DecimalFormat类主要靠 # 和 0 两种占位符号来指定数字长度;0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置
例3:

public class NumberTest {
    public static void main(String[] args) {
        double a = 123.456789;
        double b = 1.2;
        double c = 0.12345;
        long d = 123456789;

        //最少取2位整数,整数不足部分以0填补
        String s1 = new DecimalFormat("00").format(a);
        //最少取1位整数、取3位小数
        String s2 = new DecimalFormat("0.000").format(a);   
        //最少取2位整数、取3位小数,位数不足以0填补
        String s3 = new DecimalFormat("00.000").format(b);      
        //取所有整数部分
        String s4 = new DecimalFormat("#").format(a);   
        //以百分比方式计数,并最多取两位小数
        String s5 = new DecimalFormat("#.##%").format(c);   
        //以百分比方式计数,且整数部分、小数部分都保留2位,位数不足以0填补
        String s6 = new DecimalFormat("00.00%").format(b);
        //"\u2030"表示乘以1000并显示为千分数,要放在最后
        String s7 = new DecimalFormat("00.00\u2030").format(c);
        //显示为科学计数法,并取5位小数
        String s21 = new DecimalFormat("#.#####E0").format(d);  
        //显示为2位整数的科学计数法,并取4位小数
        String s22 = new DecimalFormat("00.####E0").format(d);  
        //每3位以逗号进行分隔
        String s23 = new DecimalFormat(",###").format(d);
        //每3位以逗号进行分隔,且最少三位
        String s24 = new DecimalFormat(",000").format(b);
        //将格式嵌入文本
        String s25 = new DecimalFormat("嵌入的数字是,###这个数").format(d);

        //用#和0的唯一区别是0在数位不足时会自动补足
        String s31 = new DecimalFormat("00.00").format(a);
        String s32 = new DecimalFormat("##.##").format(a);
        String s33 = new DecimalFormat("00.00").format(b);
        String s34 = new DecimalFormat("##.##").format(b);

        //可以用applyPattern()方法修改Format的模式
        DecimalFormat sf = new DecimalFormat("00");
        String s41 = sf.format(a);
        sf.applyPattern("0.000");
        String s42 = sf.format(a);

        System.out.println("s1->" + s1);
        System.out.println("s2->" + s2);
        System.out.println("s3->" + s3);
        System.out.println("s4->" + s4);
        System.out.println("s5->" + s5);
        System.out.println("s6->" + s6);
        System.out.println("s7->" + s7);
        System.out.println("s21->" + s21);
        System.out.println("s22->" + s22);
        System.out.println("s23->" + s23);
        System.out.println("s24->" + s24);
        System.out.println("s25->" + s25);
        System.out.println("s31->" + s31);
        System.out.println("s32->" + s32);
        System.out.println("s33->" + s33);
        System.out.println("s34->" + s34);
        System.out.println("s41->" + s41);
        System.out.println("s42->" + s42);
    }
}

输出

s1->123
s2->123.457
s3->01.200
s4->123
s5->12.34%
s6->120.00%
s7->123.45s21->1.23457E8
s22->12.3457E7
s23->123,456,789
s24->001
s25->嵌入的数字是123,456,789这个数
s31->123.46
s32->123.46
s33->01.20
s34->1.2
s41->123
s42->123.457

备注:用#和0的唯一区别是0在数位不足时会自动补足。更多Format模式详情请参考JDK文档。
3、BigDecimal类
  float、double类型的主要设计目标是为了科学计算和工程计算,并没有提供完全精确的结果;java.math包下的BigDecimal类则可以满足高精度运算结果,通常用于商业上的精确运算。
  BigDecimal类运算结果精确,也可用于大数的运算,但BigDecimal类的运算是通过构造函数创建运算对象,然后对对象进行运算,因此,其不能像 int 、float 、double 、long 等数据类型的数字,直接使用+ 、- 、* 、/ 等算术运算符对其对象进行数学运算 ,而必须调用其相对应的方法进行运算。
其常用构造方法如下:

序号方法描述
1public BigDecimal(double val);将double表示形式转换为BigDecimal
2public BigDecimal(int val);将int表示形式转换为BigDecimal
3public BigDecimal(long val);将long表示形式转换为BigDecimal
4public BigDecimal(String val);将String表示形式转换为BigDecimal

其中,因为String构造方法的结果是完全可预知的, 所以通常建议优先使用
其常用运算方法如下:

序号方法描述
1public BigDecimal add(BigDecimal augend);加法
2public BigDecimal subtract(BigDecimal subtrahend);减法
3public BigDecimal multiply(BigDecimal multiplicand);乘法
4public BigDecimal divide(BigDecimal divisor);除法; 如果准确的商值没有无穷的十进制扩展,抛ArithmeticException异常
5public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode);除法;设置精确位数、保留位数的策略。RoundingMode为舍入模式,更多舍入模式请查阅API文档
6public int compareTo(BigDecimal val);比较;当此 BigDecimal 在数字上小于、等于或大于 val 时,返回 -1、0 或 1
7public int scale();返回此 BigDecimal 的标度(即小数点后位数)
8public BigDecimal setScale(int newScale, RoundingMode roundingMode);返回 BigDecimal,其标度为指定值,其非标度值通过此 BigDecimal 的非标度值乘以或除以十的适当次幂来确定,以维护其总值

例4:

public class NumberTest {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("12.345");
        BigDecimal b = new BigDecimal("6.78");
        String s1 = a.add(b).toString();
        String s2 = a.subtract(b).toString();
        String s3 = a.multiply(b).toString();
        String s4 = a.divide(b,5,RoundingMode.HALF_UP).toString(); //四舍五入
        String s5 = new BigDecimal("10").divide(new BigDecimal("4")).toString(); //商要是有限位数,否侧会抛异常

        System.out.println("s1:a+b=" + s1);
        System.out.println("s2:a-b=" + s2);
        System.out.println("s3:a*b=" + s3);
        System.out.println("s4:a/b=" + s4);
        System.out.println("s5->" + s5);
    }
}

输出

s1:a+b=19.125
s2:a-b=5.565
s3:a*b=83.69910
s4:a/b=1.82080
s5->2.5

三、处理数字精度问题常用方法
  通过以上NumberFormat类、DecimalFormat类、BigDecimal类这3个类的简单介绍,我们可以知道,在不用的应用场景下,通过灵活运用这3个类及相关类,我们就可以实现高精度的运算。具体用法可参照以上介绍,下面对数字运算做一些其它方面的补充。
  1、在业务系统涉及到金额时,不少人的做法是,金额的单位为(业务显示一般为元),然后运算、DB存储时均使用double类型、保留2位小数。其实这样很容易造成精度的丢失,更合适的做法是:我们用来表示金额的单位,在运算的时候直接用int、long数据类型,最后显示的时候再转化成元,这样的话,很大程度上就避免精度丢失的问题了。(金额运算大多数为加、减,乘、除较少)
  2、可以合理地运用一些第三方工具包,如:apache.commons.lang3包中的math包,其Fraction 类可用于分数的计算、NumberUtils类可用于数字大小比较、RandomUtils类可用于随机数操作等。

四、总结
1、存储计算金额时,最好直接存整数(表示单位为分、厘、毫),然后直接对整数进行加减运算,最后在最终展示的时候,再换算成所需的单位。
2、需要保证精度的运算最好使用BigDecimal类,因为其精度准确,且与其它基本数据类型装换方便。
3、合理利用一些成熟可靠的第三方工具类,可以给数字相关运算带来很大的便利。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值