当前位置:首页 > Java技术 > BigDecimal的使用和一些坑

BigDecimal的使用和一些坑

2022年11月08日 14:51:14Java技术8

在金融领域进行业务计算时,因为要保证精度的准确,通常不建议使用float和double进行数学计算。
float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以我们不应该用于精确计算的场合。float和double类型尤其不适合用于货币运算,因为要让一个float或double精确的表示0.1或者10的任何其他负数次方值是不可能的(其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10)

为什么浮点型运算会造成精度丢失

首先我们要搞清楚下面两个问题:

 (1) 十进制整数如何转化为二进制数

       算法很简单。举个例子,11表示成二进制数:

                 11/2=5 余   1

                   5/2=2   余   1

                   2/2=1   余   0

                   1/2=0   余   1

                      0结束         11二进制表示为(从下往上):1011

      这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。

  (2) 十进制小数如何转化为二进制数

       算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数

                 0.9*2=1.8   取整数部分 1

                 0.8(1.8的小数部分)*2=1.6    取整数部分 1

                 0.6*2=1.2   取整数部分 1

                 0.2*2=0.4   取整数部分 0

                 0.4*2=0.8   取整数部分 0

                 0.8*2=1.6 取整数部分 1

                 0.6*2=1.2   取整数部分 0

                          .........      0.9二进制表示为(从上往下): 1100100100100......

       注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。

所以在进行精确计算时,java中推荐使用BigDecimal,同样在BigDecimal的使用中也有一些需要注意的地方,如果注意就可能导致同样的精度损失问题。

1.在bigDecimal初始化时,不要使用double或者float类型的值传入构造器

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。

对比可知,test2仍然会出现精度问题,而在创建BigDecimal对象时,参数为字符串就不会出现精度问题
所以总结如下


```java
//使用String构造
BigDecimal b1 = new BigDecimal("0.1");
//或者是:
BigDecimal b1 = BigDecimal.valueOf(0.1);
//点开valueOf的源码,可以看到在源码中也是用new BigDecimal(String);返回一个BigDecimal对象的
//源码如下:
public static BigDecimal valueOf(double val) {
    // Reminder: a zero double returns '0.0', so we cannot fastpath
    // to use the constant ZERO.  This might be important enough to
    // justify a factory approach, a cache, or a few private
    // constants, later.
    return new BigDecimal(Double.toString(val));
}

``

2.在进行BigDecimal数值比较时不要使用equals进行比较

使用equals进行比较会比较值的大小和精度的大小,即0.00和0.000是不相等的,要使用compareTo()来进行比较。

BigDecimal使用中的报错

Division is undefined错误

  1. 此错误仅在使用BigDecimal做除法时,且0/0的情况下才会提示。
    x/0时,仅提示Division by zero。
  2. BigDecimal判断一个值是否为0时,不能使用equals,因为equals会比较值的大小和精度的大小,即0.00 和 0.000是不同的。
    需要使用 x.compareTo(BigDecimal.ZERO) == 0 来判断。
  3. 且使用divide做除法时,标准的形式为 x.divide(y, scale,rm)
    如果不指明scale的值,会默认使用 x.scale - y.scale的值替代,如果此时值为负数,则会报错 Division undefined。
    且不指定scale 和 rm四舍五入方式,如果遇到 1/3 这种除不尽的情况,会报如下错误:
    Non-terminating decimal expansion; no exact representable decimal result.

BigDecimalUtil工具类 --解决精度问题


```java
public class BigDecimalUtil {
     
    //防止工具类在外部实例化
    private BigDecimalUtil(){
     
    }
    public static BigDecimal add(double v1, double v2){
     
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.add(b2);
    }
    public static BigDecimal sub(double v1,double v2){
     
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.subtract(b2);
    }
    public static BigDecimal mul(double v1,double v2){
     
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.multiply(b2);
    }
    public static BigDecimal div(double v1,double v2){
     
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        return b1.divide(b2,2,BigDecimal.ROUND_HALF_UP);//四舍五入,保留2位小数
    }
}

``

BigDecimal的开平方方法,使用牛顿迭代法

BigDecimal的使用和一些坑 _ JavaClub全栈架构师技术笔记
在浮点数进行计算时可以使用Math.sqrt()方法进行开方运算,在使用BigDecimal时没有提供开方方法,需要我们自己实现。
牛顿迭代法
牛顿迭代法,又称切线法,由牛顿首次提出。其算法详细过程如下图所示:

以方程 x2=nx2=n 为例,令 f(x)=x2−nf(x)=x2−n,也就是相当于求解 f(x)=0f(x)=0 的解。首先随便找一个初始值 x0x0,如果 x0x0 不是解,做一个经过 (x0,f(x0))(x0,f(x0)) 这个点的切线,与轴的交点为 x1x1。同理,如果 x1x1 不是解,做一个经过(x1,f(x1))(x1,f(x1)) 这个点的切线,与轴的交点为 x2x2。 以此类推……以这样的方式得到的会无限趋近于 f(x)=0f(x)=0 的解。

判断是否是的解有两种方法:1. 直接计算的值判断f(x)是否为0;2. 判断前后两个解和是否无限接近。

经过这个点 (xi,f(xi))(xi,f(xi)) 的切线方程为 f(x)=f(xi)+f′(xi)(x−xi)f(x)=f(xi)+f′(xi)(x−xi),
其中,f′(xi)f′(xi) 为 f(xi)f(xi) 的导数,本题中导数为 2x2x。令切线方程等于0(纵轴截距取0),即可求出:
xi+1=xi−f(xi)f′(xi)
xi+1=xi−f(xi)f′(xi)

带入 f(x)=x2−nf(x)=x2−n,继续化简:
xi+1=xi−x2i−n2xi=xi−xi2+n2xi=xi2+n2xi
xi+1=xi−xi2−n2xi=xi−xi2+n2xi=xi2+n2xi

基于上述迭代公式,可以给出了一个求平方根的算法。事实上,这也的确是很多语言中内置的开平方函数的实现方法。牛顿迭代法也同样适用于求解其他多次方程的解。

private BigDecimal sqrt(BigDecimal value, int scale) {
     
    if(value.compareTo(BigDecimal.ZERO)==0){
     
        return new BigDecimal(0);
    }
    BigDecimal num2 = BigDecimal.valueOf(2);
    int precision = 100;
    MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
    BigDecimal deviation = value;
    int cnt = 0;
    while (cnt < precision) {
     
        deviation = (deviation.add(value.divide(deviation, 100,BigDecimal.ROUND_HALF_UP))).divide(num2,100,BigDecimal.ROUND_HALF_UP);
        cnt++;
    }
    deviation = deviation.setScale(scale, BigDecimal.ROUND_HALF_UP);
    return deviation;
}

作者:zzMystery
来源链接:https://blog.csdn.net/zzMystery/article/details/102707272

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/68515.html

标签: BigDecimal
分享给朋友:

“BigDecimal的使用和一些坑” 的相关文章

BigDecimal 使用compareTo比较大小

1.BigDecimal的比较模式  BigDecimal b1 = new BigDecimal(0.1); BigDecimal b2 = new BigDecimal(0.2); int c = b1.compareTo(b2); // -1 c=1表示...

BigDecimal常被忽略的问题

BigDecimal常被忽略的问题

  一:相除精度丢失的问题   BigDecimal的api除法相对加减乘要实现的复杂多了,只介绍常用的我遇到的问题:   问题:两数相除,如果9/3=3整除没问题,但是10/3=0.33333333......除不尽,这里不能让电脑一直除不尽,所以BigD...

BigInteger、BigDecimal、数字格式化

BigInteger与BigDecimal的目的是用来精确地表示大整数和小数,常用于商业计算中。 BigInteger:支持任意精度的整数,可以精确地表示任意大小的整数值,同时在运算过程中不会丢失任何信息。 BigDecimal:可以精确地表示任意精度的小数,同时在运算过程...

Java BigDecimal 的舍入模式(RoundingMode)详解

  BigDecimal 有 8 种 RoundingMode(舍入模式),分别总结如下。 一、RoundingMode 详解 ROUND_UP 进位制:不管保留数字后面是大是小 (0 除外) 都会进 1。结果会向原点的反方向对齐,正...

BigDecimal

import java.math.BigDecimal; import java.math.RoundingMode; public class BigDecimalTest { public static void main(String[] args) {...

BigDecimal的各种使用方式

BigDecimal的使用 Bigdecimal在使用的时候不可为空,如果为空的情况建议变成BigDecimal.ZERO,这是本人工作时需要进行搜集 与整理,留作学习记录 //BigDecimal的加减乘除 //加法...

BigDecimal的使用

JAVA中有两个类BigInteger和BigDecimal分别表示大整数类和大浮点数类,至于两个类的对象能表示最大范围不清楚,理论上能够表示无线大的数,只要计算机内存足够大。 这两个类都在java.math.*包中,因此每次必须在开头处引用该包。   Ⅰ基...

java bigdecimal 存储

如果您查看BigDecimal源代码中的字段,请执行以下操作: BigDecimal: long intCompact +8 bytes int precision +4 bytes int scale +4 bytes Stri...

BigDecimal 使用方法详解

BigDecimal 使用方法详解 博客分类:   java基础 bigdecimal multiply add divide  BigD...

BigDecimal类型基本方法介绍

各位看官可以关注博主个人博客,了解更多信息。 作者:Surpasser 链接地址:https://surpass.org.cn BigDecimal是什么? Java在java.math包中提供的API类BigDecimal...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。