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

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/cndmss/article/details/52038323

一、前言
  在我们日常工作中,经常会有涉及到数字的运算,其中金额的运算尤其重要且敏感,因为金额的运算若不注意处理的话,很容易因为精度的丢失,从而导致最终数据的异常,造成严重的系统错误。本文将对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 等数据类型的数字,直接使用+ 、- 、* 、/ 等算术运算符对其对象进行数学运算 ,而必须调用其相对应的方法进行运算。
其常用构造方法如下:

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

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

序号 方法 描述
1 public BigDecimal add(BigDecimal augend); 加法
2 public BigDecimal subtract(BigDecimal subtrahend); 减法
3 public BigDecimal multiply(BigDecimal multiplicand); 乘法
4 public BigDecimal divide(BigDecimal divisor); 除法; 如果准确的商值没有无穷的十进制扩展,抛ArithmeticException异常
5 public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode); 除法;设置精确位数、保留位数的策略。RoundingMode为舍入模式,更多舍入模式请查阅API文档
6 public int compareTo(BigDecimal val); 比较;当此 BigDecimal 在数字上小于、等于或大于 val 时,返回 -1、0 或 1
7 public int scale(); 返回此 BigDecimal 的标度(即小数点后位数)
8 public 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、合理利用一些成熟可靠的第三方工具类,可以给数字相关运算带来很大的便利。

展开阅读全文

java 二维数组数字运算

11-12

这是一个数据均匀分布问题;问题描述:rn1.参数如下:rn比如随意一个正整数;比如:54rn有一个二维数组,数组分布格式为:rn100 100 100 100 200 250 250 rn150 150 100 100 200 250 150rn70 130 150 100 150 250 250rn200 200 200 200 200 200 200rn0 100 600 450 320 700 900rn230 200 300 300 300 300 300rnrn以上二维数组中的数据都是百分比*10000得到的结果;然而现在需要根据传入的54参数分布到这个二维数组中。得到每个坐标中的每个位置他实际占有的值为多少。分布数据的时候;有优先级别;例如:rn0 100 600 450 320 700 900rn这条数据的总和是最高的;所以他优先分配的等级为最高。他的总和为:3070;所以该条数据能分配的总个数为:rnint a = (3070/10000)*54rn得到该行能分配的数据后。具体去实现里面子下标中的具体数据;同时需要注意a是否大于1;如果a值小于或者等于1;则表示后面一句不在需要分配了。后面的几组坐标数据全部设置为:0.因为必须保证每个位置的数据为正整数。rnrn一行数组的子坐标同样需要根据优先级别来分;继续用上条数据为例子:rn0 100 600 450 320 700 900rn在分配数据“a”到各个子节点的时候;优先考虑900这个坐标的位置;其他以此类推。rn求值方式为:以:600为例子:int b = (600/10000)*54rn如果b的值小于1;则四舍五入为:1;没一个坐标都要判断一句发布的总和是否已经大于“a”(该行的总和),如果已经 = a;则该行后面的数据都为0.rnrn以此类推;其中需要注意的是:无论执行哪一步;都需要注意发布数据的总和不能大于实际总和;如果发现将要大于总和;下条数据用(实际总和-发布的总和)来补充;然后其他的数据都用0填充。rnrnrnrn这玩意哪位朋友能用又效率的方法写出来不。rn[img=https://forum.csdn.net/PointForum/ui/scripts/csdn/Plugin/003/monkey/2.gif][/img] 论坛

没有更多推荐了,返回首页