【Java】BigDecimal类型——BigDecimal 为什么可以保证精度不丢失

简介

  • BigDecimal是Java中的一个类,用于处理大数运算。它提供了精确的数值计算,可以处理任意位数的整数和小数,避免了使用浮点数时可能出现的精度问题。

  • 在普通的数值计算中,使用Java的基本数据类型(如int、double)进行运算时,有时会出现精度丢失的情况。例如,计算0.1 + 0.2时,使用double类型会得到0.30000000000000004,而不是预期的0.3。

  • BigDecimal类提供了一系列的构造方法,可以将基本数据类型、字符串等转换为BigDecimal对象。通过BigDecimal对象,可以进行加减乘除、取模、取整、取精度等运算操作。在进行运算时,BigDecimal会尽量保持精度,避免精度丢失。

  • 除了基本的数值运算,BigDecimal还提供了比较、取绝对值、取反、取幂等方法,以及将BigDecimal转换为其他数据类型的方法。

类介绍

看一下BigDecimal的类声明以及几个属性

public class BigDecimal extends Number implements Comparable<BigDecimal> {
    // 该BigDecimal的未缩放值
    private final BigInteger intVal;
    // 精度,可以理解成小数点后的位数
    private final int scale;
    // BigDecimal中的十进制位数,如果位数未知,则为0(备用信息)
    private transient int precision;
    // Used to store the canonical string representation, if computed.
    // 这个我理解就是存实际的BigDecimal值
    private transient String stringCache;
    // 扩大成long型数值后的值
    private final transient long intCompact;
}

案例分析

@Test
public void testBigDecimal() {
    BigDecimal bigDecimal1 = BigDecimal.valueOf(2.36);
    BigDecimal bigDecimal2 = BigDecimal.valueOf(3.5);
    BigDecimal resDecimal = bigDecimal1.add(bigDecimal2);
    System.out.println(resDecimal);
}

在执行了BigDecimal.valueOf(2.36)后,查看debug信息可以发现上述提到的几个属性被赋了值
在这里插入图片描述

进到add方法里面,看看它是怎么计算的

    // Arithmetic Operations
    /**
     * Returns a {@code BigDecimal} whose value is {@code (this +
     * augend)}, and whose scale is {@code max(this.scale(),
     * augend.scale())}.
     *
     * @param  augend value to be added to this {@code BigDecimal}.
     * @return {@code this + augend}
     */
    public BigDecimal add(BigDecimal augend) {
        if (this.intCompact != INFLATED) {
            if ((augend.intCompact != INFLATED)) {
                return add(this.intCompact, this.scale, augend.intCompact, augend.scale);
            } else {
                return add(this.intCompact, this.scale, augend.intVal, augend.scale);
            }
        } else {
            if ((augend.intCompact != INFLATED)) {
                return add(augend.intCompact, augend.scale, this.intVal, this.scale);
            } else {
                return add(this.intVal, this.scale, augend.intVal, augend.scale);
            }
        }
    }

结合传入的值来看
在这里插入图片描述
进入这个add方法来看

    private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) {
        long sdiff = (long) scale1 - scale2;
        if (sdiff == 0) {
            return add(xs, ys, scale1);
        } else if (sdiff < 0) {
            int raise = checkScale(xs,-sdiff);
            long scaledX = longMultiplyPowerTen(xs, raise);
            if (scaledX != INFLATED) {
                return add(scaledX, ys, scale2);
            } else {
                BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys);
                return ((xs^ys)>=0) ? // same sign test
                    new BigDecimal(bigsum, INFLATED, scale2, 0)
                    : valueOf(bigsum, scale2, 0);
            }
        } else {
            int raise = checkScale(ys,sdiff);
            long scaledY = longMultiplyPowerTen(ys, raise);
            if (scaledY != INFLATED) {
                return add(xs, scaledY, scale1);
            } else {
                BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs);
                return ((xs^ys)>=0) ?
                    new BigDecimal(bigsum, INFLATED, scale1, 0)
                    : valueOf(bigsum, scale1, 0);
            }
        }
    }

这个例子中,该方法传入的参数分别是:xs=236,scale1=2,ys=35,scale2=1

该方法首先计算scale1 - scale2,根据差值走不同的计算逻辑,这里求出来是1,所以进入到最下面的else代码块(这块是关键):

首先17行校验了一下数值范围:int raise = checkScale(ys,sdiff);
18行将ys扩大了10的n次倍,这里n=raise=1,所以返回的scaledY=350
在这里插入图片描述

接着就进入到20行的add方法:

    private static BigDecimal add(long xs, long ys, int scale){
        long sum = add(xs, ys);
        if (sum!=INFLATED)
            return BigDecimal.valueOf(sum, scale);
        return new BigDecimal(BigInteger.valueOf(xs).add(ys), scale);
    }

这个方法很简单,就是计算和,然后返回BigDecimal对象:
在这里插入图片描述

总结

所以可以得出结论:BigDecimal在计算时,实际会把数值扩大10的n次倍,变成一个long型整数进行计算,整数计算时自然可以实现精度不丢失。同时结合精度scale,实现最终结果的计算。

BigDecimal类型的使用场景

BigDecimal类型主要用于精确计算,特别是在需要处理大数或需要保留小数位数精确的场景中。以下是一些使用BigDecimal类型的常见场景:

  1. 财务计算:在金融或财务领域,需要对金额、利率、汇率等进行精确计算和舍入,避免由于浮点数运算带来的精度问题。

  2. 税务计算:对于需要计算税额、税率等的场景,BigDecimal类型可以确保计算结果的准确性,并避免误差。

  3. 计量单位转换:在需要进行单位转换的场景中,BigDecimal可以提供精确的计算结果,确保转换的准确性。

  4. 程序性能测试:在进行性能测试时,可能需要进行大量的计算操作,使用BigDecimal类型可以避免浮点数运算带来的精度误差,确保测试结果的准确性。

  5. 商业应用开发:在开发商业应用时,可能需要处理复杂的数值计算,如利润率、销售额等,使用BigDecimal类型可以确保计算结果的准确性,避免精度丢失。

总之,如果需要进行精确计算、保留小数位数或处理大数时,在上述场景中使用BigDecimal类型是一个很好的选择。

MySQL中存储BigDecimal类型数据

  • 在MySQL中,可以使用DECIMAL数据类型来存储BigDecimal类型的数据。DECIMAL类型用于存储精确的数值,支持指定精度和小数位数。

  • DECIMAL的语法:DECIMAL(M, D)

    • M表示总的位数,
    • D表示小数点后的位数。
  • 例如,要存储一个精确到小数点后两位的数字,可以使用DECIMAL(6,2)类型。可以在创建表时指定DECIMAL类型的字段,例如:

    CREATE TABLE example (
      id INT PRIMARY KEY,
      amount DECIMAL(10,2)
    );
    
  • 在上述例子中,amount字段的类型为DECIMAL,总共有10位,其中小数部分占2位。

  • 可以通过INSERT语句插入BigDecimal值到DECIMAL字段中,例如:

    INSERT INTO example (id, amount) VALUES (1, 1234.56);
    
  • 可以通过SELECT语句查询DECIMAL字段的值,例如:

    SELECT amount FROM example WHERE id = 1;
    

注意,需要根据实际情况来确定DECIMAL字段的合适的精度和小数位数,以确保存储和计算的准确性。

补充:BigDecimal类型使用时的注意事项

在使用BigDecimal类型时,有一些注意事项需要注意:

  1. 避免使用浮点数构造BigDecimal:浮点数在计算机中以二进制表示,可能会导致精度丢失。建议使用字符串或整数构造BigDecimal对象,以确保精确性。

  2. 使用BigDecimal的字符串构造函数:使用BigDecimal的字符串构造函数可以确保精确性,例如:new BigDecimal(“0.1”)。而使用new BigDecimal(0.1)可能会导致精度丢失。

  3. 设置精度和舍入模式:可以使用setScale()方法设置BigDecimal的精度和舍入模式。精度指的是小数部分的位数,舍入模式指的是如何处理小数位数超过精度的情况。

  4. 避免使用equals()方法比较BigDecimal:由于BigDecimal是一个引用类型,使用equals()方法比较BigDecimal对象时,需要确保比较的精确度。推荐使用compareTo()方法来进行比较。

  5. 避免使用BigDecimal进行大量的运算:由于BigDecimal对象是不可变的,每次进行运算都会创建一个新的BigDecimal对象。如果需要进行大量的计算,可以考虑使用BigDecimal的可变版本MutableBigDecimal。

  6. 注意处理除法运算的精度:在进行除法运算时,需要注意处理小数位数的精度和舍入模式。可以使用divide()方法指定精度和舍入模式。

总之,使用BigDecimal时需要注意精度、舍入模式和数值的构造方式,以确保计算的准确性和一致性。同时,了解和使用BigDecimal的各种方法,可以更好地处理数值计算和比较。

BigDecimal类型的其他使用

除了上述注意事项,以下是一些BigDecimal类型的其他使用方法:

  1. 加法和减法:可以使用add()方法进行两个BigDecimal对象的相加,使用subtract()方法进行相减。例如:

       BigDecimal num1 = new BigDecimal("10.5");
       BigDecimal num2 = new BigDecimal("5.3");
       BigDecimal sum = num1.add(num2);  // 结果为15.8
       BigDecimal difference = num1.subtract(num2);  // 结果为5.2
    
  2. 乘法和除法:可以使用multiply()方法进行乘法运算,使用divide()方法进行除法运算。例如:

       BigDecimal num1 = new BigDecimal("10.5");
       BigDecimal num2 = new BigDecimal("2.5");
       BigDecimal product = num1.multiply(num2);  // 结果为26.25
       BigDecimal quotient = num1.divide(num2);  // 结果为4.2
    
    
  3. 取反操作:可以使用negate()方法对BigDecimal对象进行取反操作。例如:

       BigDecimal num = new BigDecimal("10.5");
       BigDecimal neg = num.negate();  // 结果为-10.5
    
  4. 取绝对值:可以使用abs()方法获取BigDecimal对象的绝对值。例如:

       BigDecimal num = new BigDecimal("-10.5");
       BigDecimal abs = num.abs();  // 结果为10.5
    
  5. 比较大小:可以使用compareTo()方法对两个BigDecimal对象进行大小比较。例如:

       BigDecimal num1 = new BigDecimal("10.5");
       BigDecimal num2 = new BigDecimal("5.3");
       int result = num1.compareTo(num2);  // 结果为1(num1大于num2)
    
  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值