专栏首页Java后端技术栈cwnait【刨根问底】BigDecimal 案例和部分源码分析

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

前言

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

BigDecimal定义

不变的,任意精度的带符号的十进制数字。A BigDecimal由任意精度整数未缩放
值和32位整数级别组成 。如果为零或正数,则刻度是小数点右侧的位数。如果
是负数,则数字的非标定值乘以10,以达到等级的否定的幂。因此,BigDecimal
所代表的BigDecimal值为(unscaledValue × 10-scale) 。
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);
    }
}

部分源码分析

以下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坑

案例

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模式截图:

使用建议

  • double 参数的构造方法,不允许使用!!!!因为它不能精确的得到相应的值;
  • String 构造方法是完全可预知的: 写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好等于预期的0.1; 因此,通常建议优先使用 String 构造方法;
  • 静态方法 valueOf(double val) 内部实现,仍是将 double 类型转为 String 类型; 这通常是将 double(或float)转化为 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源码解析完成,至少要写成五篇文章,因为公众号每篇文字数不能超过五千。

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

参考:https://blog.csdn.net/en_joker/article/details/86589691

完整版源码解析,感兴趣的自行去看。

本文分享自微信公众号 - Java后端技术栈(t-j20120622),作者:lawt

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-07

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 记一次OOM问题排查过程

    看线程名称应该是tomcat的nio工作线程,线程在处理程序的时候因为无法在堆中分配更多内存出现了OOM,幸好JVM启动参数配置了-XX:+HeapDumpOn...

    用户4143945
  • 10个Java开发人员的顶级测试工具、库和框架介绍

    最近,我写了一些关于Java开发人员今年应该学习什么的文章,例如编程语言、库 和 框架,但如果你只有一件事需要改进或学习,那么那必须是你的自动化测试技能。

    用户4143945
  • 漫画:架构师是吧?什么是哈希轮?

    小码收到猎头小姐姐的面试邀约后,认真进行了准备,并在约定时间到达了面试公司....

    用户4143945
  • Java-BigDecimal数据类型

    我们知道在Java中有float和double类型,它们的主要设计目标是为了科学计算和工程计算。然而,它们没有提供完全精确的结果【因为其有限的有效位数】,所以不...

    老九学堂-小师弟
  • 为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?

    BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。

    敖丙
  • Java开发中商业计算请务必使用BigDecimal来进行计算!

    今天群里一个初级开发者问为什么测试人员测出来他写的价格计算模块有计算偏差的问题,他检查了半天也没找出问题。这里小胖哥要提醒你,商业计算请务必使用`BigDeci...

    码农小胖哥
  • 【答疑解惑】Java中的高精度数字

    前几天网友在群里有问BigDecimal能直接赋值吗?就像使用基本数据类型那样,答案是不能。 Java中的基本数据类型有的时候是不能满足实际编程需要的,特别是在...

    程序员互动联盟
  • Java中的BigDecimal类你了解多少?

    可以看到在Java中进行浮点数运算的时候,会出现丢失精度的问题。那么我们如果在进行商品价格计算的时候,就会出现问题。很有可能造成我们手中有0.06元,却无法购买...

    用户1516716
  • java中Integer运算保留2位小数

    1.整数型运算时,结果会自动去除小数点后面的部分,如果需要适当的保留几位小数,需要转为fload类型,分子或者分母或者都转

    IT云清
  • [十七]基础类型BigDecimal简介

    BigDecimal表示的数为: unscaledValue × 10的-scale 次幂

    noteless

扫码关注云+社区

领取腾讯云代金券