【Java常用类】1-5 BigDecimal 类

§1-5 BigDecimal

1-5.1 浮点类型的精度丢失

在基础课程,我们已经提及过浮点类型的存储方式。浮点类型采用近似表示的方式存储,在计算时会因为精度丢失造成计算误差的问题。

下面有一段测试浮点类型的精度丢失的 Java 程序:

public class PrecisionLoss {
    public static void main(String[] args) {
        double d1 = 1.0;
        double d2 = 0.9;
        System.out.println(d1 - d2);  //是否等于 0.1?

        d1 = 0.1;
        d2 = 0.2;
        System.out.println(d1 + d2);   //是否等于0.3?
        
        d1 = 1.4;
        d2 = 0.5;
        double d3 = 0.9;
        System.out.println((d1-d2) / d3);   //是否等于1?
    }
}

编译后运行:

0.09999999999999998
0.30000000000000004
0.9999999999999999

计算结果发生了精度丢失。同样地,让 Python 计算同样的算式,得到:

>>> 0.1+0.2
0.30000000000000004
>>> 1-0.9
0.09999999999999998
>>> (1.4-0.5)/0.9
0.9999999999999999

这样的精度丢失,是小数在计算机中的表示方法导致的。在一些重要领域中将可能会造成灾难性后果。为防止这种现象的产生,满足精确计算的要求,我们可以使用 BigDecimal 类。

1-5.2 使用 BigDecimal 解决问题

BigDecimal 类位于 java.math 包下,继承自 Number,主要用于精确计算浮点数,以及很大的小数表示。

使用 BigDecimal,需要创建对象使用。且由于其不再属于基本数据类型,只能够通过使用内置方法进行计算。

使用 BigDecimal 解决前文所提到的算术问题。

import java.math.BigDecimal;
import java.math.RoundingMode;

public class PrecisionLossFix {
    public static void main(String[] args) {
        BigDecimal bd1 = new BigDecimal("1.0");
        BigDecimal bd2 = new BigDecimal("0.9");

        BigDecimal res1 = bd1.subtract(bd2);
        System.out.println("1.0 - 0.9 = " + res1);

        BigDecimal bd3 = new BigDecimal("0.1");
        BigDecimal bd4 = new BigDecimal("0.2");

        BigDecimal res2 = bd3.add(bd4);
        System.out.println("0.1 + 0.2 = " + res2);

        BigDecimal res3 = new BigDecimal("1.4")
                .subtract(new BigDecimal("1.5"))
                .divide(new BigDecimal("0.9"));
        System.out.println("(1.4-0.5) / 0.9 = " + res3);
    }
}

编译运行,得到

1.0 - 0.9 = 0.1
0.1 + 0.2 = 0.3
(1.4-0.5) / 0.9 = 1

1-5.3 使用 BigDecimal

构造方法

构造方法 描述
BigDecimal(String val) BigDecimal 的字符串表示形式转换为 BigDecimal
BigDecimal(double val) double 转换为 BigDecimal,这是 double 的二进制浮点值的精确十进制表示
BigDecimal(BigInteger val) BigInteger 翻译成 BigDecimal

静态方法

静态方法 描述
static BigDecimal valueOf(double val) 使用 Double.toString(double) 方法提供的 double 的规范字符串表示将 double 转换为 BigDecimal
static BigDecimal valueOf(long val) long 值转换为标度为零的 BigDecimal

建议方法

  1. BigDecimal 具有很多构造方法,建议使用 BigDecimal(String val) 方法。若使用浮点数的传参重载,其一定的不可预知性可能导致创建的对象值并不精确等于预想的值;
  2. 使用 BigInteger 的传参重载构造方法,得到的 BigDecimalscale 为零(即精确到小数点后几位);
  3. 如果使用的数字不超过 double 的范围,建议使用静态方法;若使用的数字超出范围,建议使用构造方法;
  4. 类似于 Integer 缓冲区,对于传入整型的静态重载方法,若传入的参数范围位于 \([0, 10]\),则返回已有对象,否则新建对象;

常用方法

方法 描述
BigDecimal add(BigDecimal val) 加法
BigDecimal subtract(BigDecimal val) 减法
BigDecimal multiply(BigDecimal val) 乘法
BigDecimal divide(BigDecimal divisor) 除法
BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) 除法,指定保留小数位和舍入模式
BigDecimal remainder(BigDecimal val) 返回余数
BigDecimal negate() 返回相反数,精确值不变
BigDecimal pow(int n) 返回幂,精确计算至无限精度
BigDecimal sqrt(MathContext mc) 返回平方根的近似值,根据上下文设置舍入
BigDecimal max/min(BigDecimal val) 返回最值
double doubleValue() 返回该 BigDecimaldouble 转换
double intValueExact() 返回该 BigDecimalint 转换,并检查丢失信息
boolean equals() 判断二者数值是否相等
int compareTo() 判断二者数值是否相等,大于返回 1,等于返回 0,小于返回 -1

注意

  1. 对于无法整除而造成除不尽的除法,应当使用带有三个参数的重载版本,否则会抛出 ArithmeticException 异常;
  2. RoundingMode 枚举常量在下文列出;
  3. int intValue() 方法在转换过程中,若 BigDecimal 太大而无法放入 int 中,则仅返回低位 32 位。此转换可能会丢失有关 BigInteger 整体大小的信息,并返回一个带有相反符号的结果。同样地,类似的情况也会发生在 doubleValue()longValue() 方法中,对于 double,若量级太大而无法表示,则会根据需要转换为 double_NEGATIVE_INFINITYdouble_POSITIVE_INFINITY
  4. int intValueExact() 方法在转换过程中,若转换超出范围,无法放入 int 中,则抛出异常 ArithmeticException。类似地,相同的情况也会发生在 byteValueExact()longValueExact() 等方法中;
  5. 平方根方法中的参数来自于类 MathContext 所定义的枚举常量,在下文列出;
  6. 返回最值时,方法不会创建新的对象,而是返回符合要求的对象。

1-5.4 注意事项

  1. 注意创建对象时,传入参数必须合法:字符串中不可包含非法字符;

  2. 构造完成后,对象中的值不可变;

  3. BigDecimal 支持一些基本的数学运算,通过方法来包装:四则运算、幂运算、平方根、绝对值、相反数、最值;

  4. BigDecimal 中的一些常量已被弃用,代之以 RoundingMode 下的常量;

  5. 若除法计算无法整除,出现无限小数、计算出 NaN 或精确无穷(例如除以零),则会抛出异常 ArithmeticException

  6. RoundingMode 下的常量列表

    枚举常量 描述
    RoudingMode.CEILING 向正无穷舍入。对于正数,行为同 RoundingMode.UP,对于负数,行为同 RoundingMode.DOWN。注意,该舍入模式不会减小已计算的值。
    RoundingMode.FLOOR 向负无穷舍入。对于正数,行为同 RoundingMode.DOWN,对于负数,行为同 RoundingMode.UP。注意,该舍入模式不会增大已计算的值。
    RoundingMode.HALF_UP 四舍五入,向最近的整数舍入,若有等距临近整数,则向上舍入。若舍弃的小数部分 ≥ 0.5,行为等同 RoundingMode.UP;否则,行为等同 RoundingMode.DOWN
    RoundingMode.HALF_DOWN 向最近的整数舍入,若有等距临近整数,则向下舍入。若舍弃的小数部分 >0.5,行为等同 RoundingMode.UP;否则,行为等同 RoundingMode.DOWN
    RoundingMode.HALF_EVEN 银行家舍入,向最近的整数舍入,若有等距临近整数,向偶数舍入。若整数部分为奇数,行为同 RoundingMode.HALF_UP;为偶数则同 RoundingMode.HALF_DOWN
    RoundingMode.UP 向远离 0 的方向舍入。总是让具有非零舍弃的小数部分的数字递增(increments)。注意,该模式不会减小已计算的值的大小(magnitude)。
    RoundingMode.DOWN 向靠近 0 的方向舍入。从不让具有非零舍弃的小数部分的数字递增(截短)。注意,该模式不会增大已计算的值的大小。
    RoundingMode.UNNECESSARY 用于判定请求的运算是否具有精确值,因此没有必要做舍入。若运算产生非精确值,将抛出异常 ArithmeticException
  7. MathContext 中的枚举常量

    枚举常量 描述
    DECIMAL128 其精度设置与 IEEE 754-2019 decimal128 格式的精度、34 位数字和 HALF_EVEN 的舍入模式相匹配。
    DECIMAL32 其精度设置与 IEEE 754-2019 decimal32 格式的精度、7 位数字和 HALF_EVEN 的舍入模式相匹配。
    DECIMAL64 其精度设置与 IEEE 754-2019 decimal64 格式的精度、16 位数字和 HALF_EVEN 的舍入模式相匹配。
    UNLIMITED 其精度设置具有无限精度算术所需的值。

1-5.5 比较数值

BigDecimal 类中,定义了两个方法:equals();compareTo();

二者的区别是,前者比较的是数值和比例(scale),仅在而这都相同的情况下返回 true,而后者只比较数值,小于返回 -1,等于返回 0,大于返回 1

示例

public class DecimalComparison {
    public static void main(String[] args) {
        BigDecimal bd1 = new BigDecimal("2.0");
        BigDecimal bd2 = new BigDecimal("2.00");
        BigDecimal bd3 = new BigDecimal("1.00");

        //比较
        System.out.println("equals: " + bd1.equals(bd2));
        System.out.println("compare: " + bd1.compareTo(bd2));
        System.out.println("compare: " + bd2.compareTo(bd3));
        System.out.println("compare: " + bd3.compareTo(bd2));
    }
}

得到

equals: false
compare: 0
compare: 1
compare: -1

因此,比较数值大小,我们常用 compareTo() 方法。

1-5.6 底层原理

BigDecimal 的存储原理如下:

以数字 -1.5 为例

  1. 将传入的数字以字符串的形式遍历,得到每个字符的 ASCII 编码值,存储到 byte 类型数组中;

    本例,转换得到 45, 49, 46, 53

  2. 以这种方式存储,可节约空间。

java 中的数组长度上限为 int 上限(约 21 亿),可近似认为 BigDecimal 所能存储的小数接近无限。

1-5.7 格式化

请见: Java之BigDecimal详解 - 华仔Coding - 博客园 (cnblogs.com)

使用 java.text 包下的 NumberFormat 类格式化 BigDecimal

posted @ 2023-07-19 23:30  Zebt  阅读(51)  评论(0)    收藏  举报