当前位置 博文首页 > 一条IT:【BigDecimal】大数的精确计算你还在用Double?

    一条IT:【BigDecimal】大数的精确计算你还在用Double?

    作者:[db:作者] 时间:2021-08-13 12:47

    嗨,大家好,我是一条。

    感谢大家的支持,一条的原创文章关于debug你可能还不知道的技巧,建议所有人都看一下入选工具类排行榜第六名。

    这是第一次入榜,写作的动力更足了。

    为了让更多的人看到一条的分享,一条准备报名原力计划,报名条件是粉丝数超过2000

    所以一条现在非常需要大家的关注,如果觉得一条写的还可以,就点个关注再走吧!

    等粉丝数达到2000时,一条给大家在微信准备一个抽奖,奖品暂定键盘和手环二选一,关注微信公众号就可以参与。


    ?

    今天组长突然在群里@我,我当时就觉得情况不妙,果然

    “以后涉及金额类的大数都要用BigDecimal,不要用Double”

    “好的组长”

    ……

    BigDecimal是什么东西,该怎么用呢

    别急,一条这就和大家聊聊。

    一、BigDecimal简介

    BigDecimal是Java在java.math包中提供的API类,用来对超过16位有效位的数进行精确的运算。是不是很简单,那么问题来了

    1.为什么不用Double

    一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,比如商业计算,则必须使用BigDecimal类来操作。Float和Double只能用来做科学计算或者是工程计算。

    2.BigDecimal可以做什么

    BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

    主要是以下几种操作,也是本文的主要内容:

    • 新建BigDecimal
    • 加减乘除运算
    • 类型转换及格式化
    • 比较大小
    • 常见异常

    ?

    二、玩转BigDecimal

    加下来一条就从新建到使用带大家玩转BigDecimal,保证你的组长不会说你。冲冲冲!

    1.新建BigDecimal

    前面说BigDecimal所创建的是对象,提到对象毫无疑问new就完了

            BigDecimal num1 = new BigDecimal(0.005);
            //尽量用字符串的形式初始化
            BigDecimal num22 = new BigDecimal("1000000");

    ?

    2.加减乘除运算

    对象的加减运算用 + - * / 肯定是不管用了,不过别慌,我们有方法。

    • 加法?add()???
    • 减法subtract()
    • 乘法multiply()
    • 除法divide()
    • 绝对值abs()

    参数类型为double的构造方法的结果有一定的不可预知性。其实在Java中写入newBigDecimal(0.1)所创建的BigDecimal实际上等于

    0.1000000000000000055511151231257827021181583404541015625。

    这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

            //加法
            BigDecimal result1 = num1.add(num2);
            System.out.println(result1);  
            //如果num1用数字创建,结果为:
            //1000000.005000000000000000104083408558608425664715468883514404296875
            //如果num1用字符串创建,结果为:
            //1000000.005
    
            //减法
            BigDecimal result2 = num1.subtract(num2);
    
            //乘法
            BigDecimal result3 = num1.multiply(num2);
    
            //绝对值
            BigDecimal result4 = num1.abs();
    
            //除法
            BigDecimal result5 = num2.divide(num1,20,BigDecimal.ROUND_HALF_UP);
            System.out.println(result5);
            //结果为:
            //200000000.00000000000000000000
            //20代表保留小数位数
            //ROUND_HALF_UP代表四舍五入

    ?

    八种舍入模式

    一下八种模式皆用1/3举例,保留三位小数,结果对应写在后面

    1、ROUND_UP:始终对非零舍弃部分前面的数字加1,即始终进位。—— 0.334

    2、ROUND_DOWN:从不对舍弃部分前面的数字加1,即始终舍弃,相当于截断。 —— 0.333

    3、ROUND_CEILING:如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;如果为负,则舍入行为与 ROUND_DOWN 相同。——0.334

    4、ROUND_FLOOR:如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;如果为负,则舍入行为与 ROUND_UP 相同。——0.333

    5、ROUND_HALF_UP:如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(四舍五入)。——0.333

    6、ROUND_HALF_DOWN:如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。——0.333

    7、ROUND_HALF_EVEN:如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。——0.334

    注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。

    四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。

    8、ROUND_UNNECESSARY:断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

    ?

    3.类型转换及格式化

    主要是和String和其他基本类型的转换

    toString()? ?将BigDecimal对象中的值转换成字符串

    doubleValue()? ? 将BigDecimal对象中的值转换成双精度数

    floatValue()? ?将BigDecimal对象中的值转换成单精度数

    longValue()? ?将BigDecimal对象中的值转换成长整数

    intValue()? ? 将BigDecimal对象中的值转换成整数?

    由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

    NumberFormat?currency?=?NumberFormat.getCurrencyInstance();?//建立货币格式化引用?
    NumberFormat?percent?=?NumberFormat.getPercentInstance();??//建立百分比格式化引用?
    percent.setMaximumFractionDigits(3);?//百分比小数点最多3位?
     
    BigDecimal?loanAmount?=?new?BigDecimal("15000.48");?//贷款金额
    BigDecimal?interestRate?=?new?BigDecimal("0.008");?//利率???
    BigDecimal?interest?=?loanAmount.multiply(interestRate);?//相乘
     
    System.out.println("贷款金额:\t"?+?currency.format(loanAmount));?
    System.out.println("利率:\t"?+?percent.format(interestRate));?
    System.out.println("利息:\t"?+?currency.format(interest));?

    4.比较大小

    java中对BigDecimal比较大小一般用的是bigdemical的compareTo方法

    int?a?=?bigdemical.compareTo(BigDecimal.ZERO)
    
    
    //结果分析
    
    //a = -1,表示bigdemical小于0;
    //a = 0,表示bigdemical等于0;
    //a = 1,表示bigdemical大于0;
    
    

    5.常见异常

    通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

    解决方法:

    divide方法设置精确的小数点,如:divide(xxxxx,2)

    java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result

    ?

    三、手写工具类(建议收藏)

    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;
    ????}
    }

    最后絮叨几句

    BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal,在需要精确的小数计算时再使用BigDecimal。

    尽量使用参数类型为String的构造函数。

    BigDecimal都是不可变的,类似String。 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

    ?


    我是一条,一个在互联网摸爬滚打的程序员。

    微信关注【一条IT】,第一时间获取文章推送。

    道阻且长,行则将至。大家的?【点赞,收藏,关注】?就是一条创作的最大动力,我们下期见!

    注:关于本篇博客有任何问题和建议,欢迎大家留言!

    ?

    cs