JDK描述

public class BigDecimal
extends Number
implements Comparable

BigDecimal不可变的、任意精度的有符号十进制数。
BigDecimal由任意精度的整数非标度值 和 32 位的整数标度 (scale)组成。
BigDecimal 类提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换。toString() 方法提供 BigDecimal 的规范表示形式。(unscaledValue × 10^-scale)

BigDecimal类使用户能完全控制舍入行为。如果未指定舍入模式,并且无法表示准确结果(如1/3),则抛出一个异常;可以通过使用 RoundingMode enum(例如,RoundingMode.HALF_UP)的枚举值来设置舍入模式。

当提供一个0精度设置的MathContext对象时,(如MathContext.UNLIMITED),此时的算术运算是准确的,不提供MathContext对象时也是准确的。在除法中,商可能是一个无限长的十进制扩展,这是应该设置精度和舍入模式;例如,1 除以 3 所得的商。

当精度设置不为0时,BigDecimal算法的规则完全符合ANSI X3.274-1996和ANSI X3.274-1996/AM 1-2000( 7.4 节)中定义的算法的可选操作模式。与上述标准不同,BigDecimal包括多种舍入模式,它们对于版本5以前的BigDecimal版本中的除法是强制性的。这些ANSI标准和BigDecimal规范之间的任何冲突都按照有利于 BigDecimal 的方式进行解决。

由于同一数值可以有不同的表示形式(具有不同的标度),因此运算和舍入的规则必须同时指定数值结果和结果表示形式中所用的标度。

一般情况下,当准确结果(在除法中,可能有无限多位)比返回的数值具有更多位数时,由舍入模式和精度设置来确定操作如何返回具有有限位数的结果。 首先,MathContext的precision指定要返回的总位数;这确定了结果的精度。位数计数从准确结果的最左边的非零数字开始。最后,由舍入模式确定丢弃的尾部位数如何影响返回的结果。

对于所有算术运算符,运算的执行方式是,

  • 首先计算准确的中间结果,
  • 然后,使用选择的舍入模式将其舍入为精度设置(如有必要)指定的位数。如果不返回准确结果,则将丢弃准确结果的某些数位。当舍入增加了返回结果的大小时,前导数字“9”的进位传播可能会创建新的数位。例如,将值 999.9 舍入为三位数字,则在数值上等于一千,表示为 100×101。在这种情况下,新的 “1” 是返回结果的前导数位。

除了逻辑的准确结果外,每种算术运算都有一个表示结果的首选标度。下表列出了每个运算的首选标度。

算术运算结果的首选标度

运算	结果的首选标度
加	max(addend.scale(), augend.scale())  两个操作数的最大精度
减	max(minuend.scale(), subtrahend.scale()) 两个操作数的最大精度
乘	multiplier.scale() + multiplicand.scale() 两个操作数的精度之和
除	dividend.scale() - divisor.scale() 被除数-除数

代码分析

由jdk描述可知,BigDecimal类由unscaledValue和scale组成

BigDecimal类实现了两个接口

  • Number 基本上所有数字型都继承Numer,用于类型转换
  • Comparable 实现比较

BigDecimal中的unscaledValue若大于Long的最大值则由BigInteger类表示,BigInteger中将数字以int数组表示。

BigDecimal数组的长度和小数点精度可以有MathContex表示,计算时的舍弃方式由Enum RoundingMode表示,有8种方式:

  • RoundingMode.UP 前一位绝对值加1向上
scale=3  3232.3238  ->   3232.324
scale=3 -3232.3238  ->  -3232.324
  • RoundingMode.DOWN 丢弃
scale=3  3232.3238  ->   3232.323
scale=3 -3232.3238  ->  -3232.323
  • RoundingMode.CEILING 向正数方向走:若值为正数,与UP相同;负数与DOWN相同;
scale=3  3232.3238  ->   3232.324
scale=3 -3232.3238  ->  -3232.323
  • RoundingMode.FLOOR 向负数方向走
scale=3  3232.3238  ->   3232.323
scale=3 -3232.3238  ->  -3232.324
  • RoundingMode.HALF_UP 大于等于5则UP,否则DOWN
scale=3  3232.3234  ->   3232.323
scale=3 -3232.3234  ->  -3232.323

scale=3  3232.3235  ->   3232.324
scale=3 -3232.3235  ->  -3232.324

scale=3  3232.3236  ->   3232.324
scale=3 -3232.3236  ->  -3232.324
  • RoundingMode.HALF_DOWN 大于5则UP,否则DOWN
scale=3  3232.3234  ->   3232.323
scale=3 -3232.3234  ->  -3232.323

scale=3  3232.3235  ->   3232.323
scale=3 -3232.3235  ->  -3232.323

scale=3  3232.3236  ->   3232.324
scale=3 -3232.3236  ->  -3232.324
  • RoundingMode.HALF_EVEN 如果舍弃部分左边的数字为奇数,则舍入行为同 RoundingMode.HALF_UP;如果为偶数,则舍入行为同RoundingMode.HALF_DOWN
//奇数,走HALF_UP
scale=3  3232.3234  ->   3232.323
scale=3 -3232.3234  ->  -3232.323

scale=3  3232.3235  ->   3232.324
scale=3 -3232.3235  ->  -3232.324

scale=3  3232.3236  ->   3232.324
scale=3 -3232.3236  ->  -3232.324

//偶数,走HALF_DOWN
scale=3  3232.3244  ->   3232.324
scale=3 -3232.3244  ->  -3232.324

scale=3  3232.3245  ->   3232.324
scale=3 -3232.3245  ->  -3232.324

scale=3  3232.3246  ->   3232.325
scale=3 -3232.3246  ->  -3232.325

BigDecimal的变量

  • private volatile BigInteger intVal; BigDecimal的未scale的值,BigInteger是一个任意长度的整数

  • private int scale; //BigDecimal的scale

  • private transient int precision; //BigDecimal的数字的长度

  • private transient long intCompact; 若BigDecimal的绝对值小于Long.MAX_VALUE,放在这个变量中

  • private transient String stringCache; toString后缓存住

  • private static final BigDecimal zeroThroughTen[] //缓存0 ~ 10

  • private static final BigDecimal[] ZERO_SCALED_BY //缓存0 ~ 0E-15

构造方法

  • public BigDecimal(String val) 对字符串进行解析,最终设置scale小数的个数,precision数字的个数,数字小于Long.MAX_VALUE时放在intCompact,大于则放在intVal中。
  • public BigDecimal(String val, MathContext mc) 若mc的precision大于0,
 private void roundThis(MathContext mc) {
        BigDecimal rounded = doRound(this, mc);//根据mc计算新的BigDecimal
        if (rounded == this)                 // wasn\'t rounded
            return;
        this.intVal     = rounded.intVal;
        this.intCompact = rounded.intCompact;
        this.scale      = rounded.scale;
        this.precision  = rounded.precision;
    }
    
 private static BigDecimal doRound(BigDecimal d, MathContext mc) {
        int mcp = mc.precision;
        int drop;//保存需要丢弃的位数
        // 若指mc的precision小于BigDecimal的precision
        while ((drop = d.precision() - mcp) > 0) {
            //计算新的小数点后的位数
            int newScale = d.checkScale((long)d.scale - drop);
            int mode = mc.roundingMode.oldMode;
            if (drop < LONG_TEN_POWERS_TABLE.length)
                d = divideAndRound(d.intCompact, d.intVal,
                                   LONG_TEN_POWERS_TABLE[drop], null,
                                   newScale, mode, newScale);
            else
                d = divideAndRound(d.intCompact, d.intVal,
                                   INFLATED, bigTenToThe(drop),
                                   newScale, mode, newScale);
        }
        return d;
    }

相关问题

解释下面的程序

System.out.println(new BigDecimal(123.34));
System.out.println(new BigDecimal("123.34"));
分别输出:
123.340000000000003410605131648480892181396484375
123.34

这是因为123.32是double,精度不准确,而字符串解析后字符串精度准确

总结

  • BigDecimal内部有一个整数表示所有数字,precision表示这个数字的位数,scale表示小数后的位数
  • 除法时需要设置小数位数时注意要指定小数的舍弃方式
  • BigInteger类
  • MutableBigInteger类

版权声明:本文为ssj234原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ssj234/p/6391651.html