首页 > 编程语言 >【刨根问底】BigDecimal 案例和部分源码分析

【刨根问底】BigDecimal 案例和部分源码分析

时间:2023-06-08 22:35:44浏览次数:53  
标签:BigDecimal int double 源码 b1 刨根问底 offset public

本文总以下几个部分:

  1. 前言
  2. Bigdecimal定义
  3. Bigdecimal创建方式
  4. Bigdecimal部分源码分析
  5. Bigdecimal坑
  6. Bigdecimal使用建议
  7. Bigdecimal工具类

【刨根问底】BigDecimal 案例和部分源码分析_构造方法


前言


在咱们开发过程中很容易遇到计算的问题,普通计算其实也还好使用int、long、double、float基本上能应付。但是如果涉及到数据类型转后在处理等就不是很好做,于是这会Bigdecimal就出现了。


【刨根问底】BigDecimal 案例和部分源码分析_构造方法


BigDecimal定义


不变的,任意精度的带符号的十进制数字。A BigDecimal由任意精度整数未缩放	
值和32位整数级别组成 。如果为零或正数,则刻度是小数点右侧的位数。如果	
是负数,则数字的非标定值乘以10,以达到等级的否定的幂。因此,BigDecimal	
所代表的BigDecimal值为(unscaledValue × 10-scale) 。	
BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换


【刨根问底】BigDecimal 案例和部分源码分析_构造方法


BigDecimal的创建方式


/**	
 * @author Java后端技术栈	
 * @date 2019/7/6	
 */	
public class BigDecimalDemo {	
    public static void main(String[] args) {	
        //new 形式创建	
        BigDecimal bigDecimal = new BigDecimal("1");	
        System.out.println(bigDecimal);	
        //valueOf形式创建	
        BigDecimal b1 = BigDecimal.valueOf(2.3333);	
        System.out.println(b1);	
        //BigDecimal.形式创建	
        BigDecimal b2 = BigDecimal.ZERO;	
        System.out.println(b2);	
    }	
}


【刨根问底】BigDecimal 案例和部分源码分析_构造方法


部分源码分析


以下JDK版本为:1.8

常用几个重要的属性


// 若BigDecimal的绝对值小于Long.MAX_VALUE,放在这个变量中	
//public static final long MIN_VALUE = 0x8000000000000000L;	
private final transient long intCompact;	
//BigDecimal的标度(小数点),	
//输入数除以10的scale次幂(32 位的整数标度)	
private final int scale;	
// BigDecimal的未scale的值,BigInteger是	
// 一个任意长度的整数(整数非标度值)	
private final BigInteger intVal;	
// BigDecimal的精度(精度是非标度值的数字个数)	
private transient int precision;	
//toString后缓存	
private transient String stringCache;


BigDecimal是没有无参构造方法的,所以这里从上面案例中的第一种创建方式使用的构造方法开始:


public BigDecimal(String val) {	
//字符串转换成char数组,offset设置为0	
  this(val.toCharArray(), 0, val.length());	
}


看得出来,这里没做什么,然后直接调用了重载的构造方法:


public BigDecimal(char[] in, int offset, int len) {	
    //MathContext.UNLIMITED这个在老版本中有使用,新版本没有使用了	
    this(in,offset,len,MathContext.UNLIMITED);	
 }


继续调用重载的方法:


/**	
     * 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 	
     *  BigDecimal(String) 构造方法相同的字符序列,同时允许指定子数组。 	
     * 注意,如果字符数组中已经提供字符的序列,则使用此构造方法要比将	
     * char 数组转换为字符串并使用 BigDecimal(String) 构造方法更快。 	
     * @param in  作为源字符的 char 数组	
     * @param offset  要检查的数组中的第一个字符	
     * @param len  要考虑的字符数	
     * @param mc  没有使用	
     */	
 public BigDecimal(char[] in, int offset, int len, MathContext mc) {	
        // 防止长度过大。	
        if (offset+len > in.length || offset < 0)	
            throw new NumberFormatException();	
      /*	
       * 这是BigDecimal构造函数的主字符串;所有传入字符串都在这里结束;	
       * 它使用显式(内联)解析来提高速度,并为非紧凑情况生成最多	
       * 一个中间(临时)对象(char[]数组)。	
       */	
        // 对所有字段值使用局部变量,直到完成	
        int prec = 0;   // BigDecimal的数字的长度	
        int scl = 0;   // BigDecimal的标度	
        long rs = 0;   // intCompact值	
        BigInteger rb = null; // BigInteger的值	

	
        // 使用数组边界检查来处理太长、len == 0、错误偏移等等。	
        try {	
            //符号的处理	
            boolean isneg = false; // '+'为false,'-'为true	
            if (in[offset] == '-') {// 第一个字符为'-'	
                isneg = true;	
                offset++;	
                len--;	
            } else if (in[offset] == '+') {// 第一个字符为'+'	
                offset++;	
                len--;	
            }	

	
            //数字有效部分	
            boolean dot = false;//当有“.”时为真。	
            int cfirst = offset; //记录integer的起始点	
            long exp = 0;  //exponent	
            char c; //当前字符	

	
            boolean isCompact = (len <= MAX_COMPACT_DIGITS);	
            // 大于18位是BigInteger,创建数组	
            char coeff[] = isCompact ? null : new char[len];	
            int idx = 0;	

	
            for (; len > 0; offset++, len--) {	
                c = in[offset];	
                // 有数字,确定c(Unicode 代码点)是否为数字	
                if ((c >= '0' && c <= '9') || Character.isDigit(c)) {	
                    // 第一个紧化情况,我们不需要保留字符我们可以就地计算值。	
                    if (isCompact) { // 非BigInteger数值	
                        // 获取使用10进制的字符 c 的数值	
                        int digit = Character.digit(c, 10);	
                        if (digit == 0) {// 为 0	
                            if (prec == 0)	
                                prec = 1;	
                            else if (rs != 0) {	
                                rs *= 10;	
                                ++prec;	
                            }// 否则,数字为冗余前导零	
                        } else { // 非0	
                            if (prec != 1 || rs != 0)	
                                ++prec; // 如果前面加0,则prec不变	
                            rs = rs * 10 + digit;	
                        }	
                    } else {// the unscaled value可能是一个BigInteger对象。	
                        if (c == '0' || Character.digit(c, 10) == 0) {// 为0	
                            if (prec == 0) {	
                                coeff[idx] = c;	
                                prec = 1;	
                            } else if (idx != 0) {	
                                coeff[idx++] = c;	
                                ++prec;	
                            } // 否则c一定是多余的前导零	
                        } else {	
                            if (prec != 1 || idx != 0)	
                                ++prec; // 如果前面加0,则prec不变	
                            coeff[idx++] = c;	
                        }	
                    }	
                    if (dot)// 如果有小数点	
                        ++scl;	
                    continue;	
                }	
                // 当前字符等于小数点	
                if (c == '.') {	
                    // have dot	
                    if (dot)// 存在两个小数点	
                        throw new NumberFormatException();	
                    dot = true;	
                    continue;	
                }	
                // exponent 预期	
                if ((c != 'e') && (c != 'E'))	
                    throw new NumberFormatException();	
                offset++;	
                c = in[offset];	
                len--;	
                boolean negexp = (c == '-'); // 当前字符是否为'-'	
                if (negexp || c == '+') { // 为符号	
                    offset++;	
                    c = in[offset];	
                    len--;	
                }	
                if (len <= 0)// 没有 exponent 数字	
                    throw new NumberFormatException();	
                // 跳过exponent中的前导零 	
                while (len > 10 && Character.digit(c, 10) == 0) {	
                    offset++;	
                    c = in[offset];	
                    len--;	
                }	
                if (len > 10) // 太多非零 exponent 数字	
                    throw new NumberFormatException();	
                // c 现在是 exponent的第一个数字	
                for (;; len--) {	
                    int v;	
                    if (c >= '0' && c <= '9') {	
                        v = c - '0';	
                    } else {	
                        v = Character.digit(c, 10);	
                        if (v < 0) // 非数字	
                            throw new NumberFormatException();	
                    }	
                    exp = exp * 10 + v;	
                    if (len == 1)	
                        break;   // 最终字符	
                    offset++;	
                    c = in[offset];	
                }	
                if (negexp)                 // 当前字符为'-',取相反数	
                    exp = -exp;	
                // 下一个测试需要向后兼容性	
                if ((int)exp != exp)         // 溢出	
                    throw new NumberFormatException();	
                break;	
            }	
            // 这里没有字符了	
            if (prec == 0)              // 没有发现数字	
                throw new NumberFormatException();	
            // 如果exp不为零,调整标度。	
            if (exp != 0) {                 // 有显著的exponent	
                // 不能调用基于正确的字段值的checkScale	
                long adjustedScale = scl - exp;	
                if (adjustedScale > Integer.MAX_VALUE ||	
                        adjustedScale < Integer.MIN_VALUE)	
                    throw new NumberFormatException("Scale out of range.");	
                scl = (int)adjustedScale;	
            }	
            // 从precision中删除前导零(数字计数)	
            if (isCompact) {	
                rs = isneg ? -rs : rs;	
            } else {	
                char quick[];	
                if (!isneg) {	
                    quick = (coeff.length != prec) ?	
                            Arrays.copyOf(coeff, prec) : coeff;	
                } else {	
                    quick = new char[prec + 1];	
                    quick[0] = '-';	
                    System.arraycopy(coeff, 0, quick, 1, prec);	
                }	
                rb = new BigInteger(quick);	
                // 获取rb(BigInteger)的compact值。	
                rs = compactValFor(rb);	
            }	
        } catch (ArrayIndexOutOfBoundsException e) {	
            throw new NumberFormatException();	
        } catch (NegativeArraySizeException e) {	
            throw new NumberFormatException();	
        }	
        this.scale = scl;	
        this.precision = prec;	
        this.intCompact = rs;	
        this.intVal = (rs != INFLATED) ? null : rb;	
    }


【刨根问底】BigDecimal 案例和部分源码分析_构造方法


BigDecimal坑


案例


public class BigDecimalDemo {	
    public static void main(String[] args) {	
        double d = 2.33;	
        BigDecimal dd = new BigDecimal(d);	
        if (d == dd.doubleValue()) {	
            System.out.println(dd);	
        }	
    }	
 }


debug模式截图:

【刨根问底】BigDecimal 案例和部分源码分析_git_06


【刨根问底】BigDecimal 案例和部分源码分析_构造方法


使用建议


  •  double 参数的构造方法,不允许使用!!!!因为它不能精确的得到相应的值;
  • String 构造方法是完全可预知的: 写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的0.1; 因此,通常建议优先使用 String 构造方法;
  • 静态方法 valueOf(double val) 内部实现,仍是将 double 类型转为 String 类型; 这通常是将 double(或float)转化为 BigDecimal 的首选方法;

【刨根问底】BigDecimal 案例和部分源码分析_构造方法


工具类


import java.math.BigDecimal;	

	
/**	
 * 使用BigDecimal来计算	
 *	
 * @author Java后端技术栈	
 * @date 2019/7/6	
 */	
public class BigDecimalUtil {	
    // 除法运算默认精度	
    private static final int DEF_DIV_SCALE = 10;	

	
    private BigDecimalUtil() {	
    }	

	
    /**	
     * 精确加法	
     */	
    public static double add(double value1, double value2) {	
        BigDecimal b1 = BigDecimal.valueOf(value1);	
        BigDecimal b2 = BigDecimal.valueOf(value2);	
        return b1.add(b2).doubleValue();	
    }	

	
    /**	
     * 精确减法	
     */	
    public static double sub(double value1, double value2) {	
        BigDecimal b1 = BigDecimal.valueOf(value1);	
        BigDecimal b2 = BigDecimal.valueOf(value2);	
        return b1.subtract(b2).doubleValue();	
    }	

	
    /**	
     * 精确乘法	
     */	
    public static double mul(double value1, double value2) {	
        BigDecimal b1 = BigDecimal.valueOf(value1);	
        BigDecimal b2 = BigDecimal.valueOf(value2);	
        return b1.multiply(b2).doubleValue();	
    }	

	
    /**	
     * 精确除法 使用默认精度	
     */	
    public static double div(double value1, double value2) throws IllegalAccessException {	
        return div(value1, value2, DEF_DIV_SCALE);	
    }	

	
    /**	
     * 精确除法	
     *	
     * @param scale 精度	
     */	
    public static double div(double value1, double value2, int scale) throws IllegalAccessException {	
        if (scale < 0) {	
            throw new IllegalAccessException("精确度不能小于0");	
        }	
        BigDecimal b1 = BigDecimal.valueOf(value1);	
        BigDecimal b2 = BigDecimal.valueOf(value2);	
        // return b1.divide(b2, scale).doubleValue();	
        return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();	
    }	

	
    /**	
     * 四舍五入	
     *	
     * @param scale 小数点后保留几位	
     */	
    public static double round(double v, int scale) throws IllegalAccessException {	
        return div(v, 1, scale);	
    }	

	
    /**	
     * 比较大小	
     * 相等返回true	
     */	
    public static boolean equalTo(BigDecimal b1, BigDecimal b2) {	
        return b1 != null && b2 != null && 0 == b1.compareTo(b2);	
    }	
}

完全把BigDecimal源码解析完成,至少要写成五篇文章,因为公众号每篇文字数不能超过五千。

【刨根问底】BigDecimal 案例和部分源码分析_数组_09

,所以在此分享一个源码阅读完整版的地址,但是JDK版本是1.6的。将就一下吧。看源码有的时候不是真的把他的设计记住,主要是能学到一些别人的思路。

标签:BigDecimal,int,double,源码,b1,刨根问底,offset,public
From: https://blog.51cto.com/u_11702014/6443827

相关文章

  • 深入源码探讨HashSet
    我们在工作中时常会用到HashSet,面试也有时候容易被问到,下面咱们就来聊聊HashSet。使用案例publicclassTest{publicstaticvoidmain(String[]args){HashSet<String>hashSet=newHashSet<>();hashSet.add("Java");hashSet.add("R");......
  • 深入探讨源码-HashMap
    又聊到HashMap了,其实网上已经有很多关乎HashMap的文章了,本文将对HashMap的实现方式和一些关键点进行一一的说明,仅限个人理解,如有疑惑和问题,请联系作者更正。说明:JDK版本1.8.0_151HashMapHash表是一个数组+链表的结构,这种结构能够保证在遍历与增删的过程中,如果不产生hash碰撞,仅需一......
  • 源码安装redis-migrate-tool(redis迁移工具)部署安装
    源码安装redis-migrate-toolredis-migrate-toolunzipredis-migrate-tool-master.zipcdredis-migrate-tool-masteryum-yinstallautomakelibtoolautoconfbzip2autoreconf-fvi./configuremake./src/redis-migrate-toolrmt.conf配置项修改[source]typ......
  • BitSet的源码研究
    这几天看BloomFilter,因为在java中,并不能像C/C++一样直接操纵bit级别的数据,所以只能另想办法替代:1)使用整数数组来替代;2)使用BitSet;BitSet实际是由“二进制位”构成的一个Vector。如果希望高效率地保存大量“开-关”信息,就应使用BitSet。它只有从尺寸的角度看才有意义;如果希望的高效率......
  • JAVA的springboot+vue学习平台管理系统,校园在线学习管理系统,附源码+数据库+论文+PPT
    1、项目介绍在Internet高速发展的今天,我们生活的各个领域都涉及到计算机的应用,其中包括学习平台的网络应用,在外国学习平台已经是很普遍的方式,不过国内的管理平台可能还处于起步阶段。学习平台具有学习信息管理功能的选择。学习平台采用java技术,基于springboot框架,mysql数据库进行......
  • Java语言实现生产者与消费者的消息队列模型(附源码)
    Java构建生产者与消费者之间的生产关系模型,可以理解生产者生产message,存放缓存的容器,同时消费者进行消费需求的消耗,如果做一个合理的比喻的话:生产者消费者问题是线程模型中的经典问题。解决生产者/消费者问题有两种方法:一是采用某种机制保护生产者和消费者之间的同步;二是在生产者和......
  • 多校园微社区交友及二手物品论坛小程序源码运营需要什么?
    在大学校园里,存在着很多的二手商品,但是由于信息资源的不流通以及传统二手商品信息交流方式的笨拙,导致了很多仍然具有一定价值或者具有非常价值的二手商品的囤积,乃至被当作废弃物处理。现在通过微信小程序的校园二手交易平台,可以方便快捷的发布和交流任何二手商品的信息,并且可以通过......
  • zabbix--CentOS7 源码安装Zabbix6 LTS版本
    环境说明#这里使用为 CentOS7.6 版本进行测试验证,ZabbixServer 采用源码包部署,数据库采用 MySQL8.0 版本,zabbix-web 使用 nginx+php 来实现。具体信息如下:软件名版本安装方式ZabbixServer6.0.3源码安装ZabbixAgent6.0.3源码安装MySQL8.0.28yum安......
  • 跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
    本文由will分享,个人博客zhangyaoo.github.io,原题“基于Netty的IM系统设计与实现”,有修订和重新排版。1、引言本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯......
  • 直播小程序源码,自定义支持360度旋转的View
    直播小程序源码,自定义支持360度旋转的View自定义Touch360ImageView的代码如下: packagecom.example.myapplication;importandroid.content.Context;importandroid.content.res.TypedArray;importandroid.graphics.drawable.LevelListDrawable;importandroid.util.Attribut......