Java基础之BigDecimal

本篇介绍float和double在某些计算中存在的缺陷以及BigDecimal的具体用法。

为什么不使用float和double

我们在做银行项目或者财务系统的时候会发现很多比如金额、价格这样的数据类型都是BigDecimal,并没有使用更常用的double或者float,其原因是double和float在做浮点运算时会存在精度缺失问题。例如下面的程序:

public class Main {

    public static void main(String[] args) {
        double d1 = 10.2;
        double d2 = 1.19;
        System.out.println("d1 * d2 = " + (d1 * d2));
	System.out.println("d1 + d2 = " + (d1 + d2));

	System.out.println("\n");

	float f1 = 10.2F;
	float f2 = 1.19F;
	System.out.println("f1 * f2 = " + (f1 * f2));
	System.out.println("f1 + f2 = " + (f1 + f2));
    }
}

 我们以为的结果是:

d1 * d2 = 12.138
d1 + d2 = 11.39

f1 * f2 = 12.138
f1 + f2 = 11.39

然而其最终结果是:

d1 * d2 = 12.137999999999998
d1 + d2 = 11.389999999999999


f1 * f2 = 12.1380005
f1 + f2 = 11.389999

为什么float和double的浮点运算会存在精度丢失

首先我们要了解十进制(带小数)怎么转换成二进制。十进制转换成二进制时,整数部分和小数部分分开计算。整数部分就是我们熟悉的除2取余法,小数部分的处理方式是乘以2得到整数部分(0或1),然后将得到的小数部分继续乘以2直到小数部分为0或达到相应的位数,与整数不同的是得到的0或1是正序排列的。例如上面提到的10.2的计算方法为:

整数部分:
10 / 2 = 5 余 0
5 / 2 = 2 余 1
2 / 2 = 1 余 0
1 / 2 = 0 余 1
因此10的二进制表示为1010。
小数部分:
0.2 * 2 = 0.4 取整 0
0.4 * 2 = 0.8 取整 0
0.8 * 2 = 1.6 取整 1
0.6 * 2 = 1.2 取整 1
0.2 * 2 = 0.4 取整 0
可以看到小数部分为0011的循环

也就是说小数部分是无法精切表示的,类似于十进制表示1/3也是没法精确表示的。

BigDecimal

BigDecimal是java.math包中的类,为不可变对象。使用十进制+小数点位数来表示小数,以此避免小数点的出现,也就防止了精度丢失。例如333.66表示为33366*0.1^2。 

其主要构造函数如下:

//将double转换为BigDecimal
BigDecimal(double val);
//将int转换为BigDecimal
BigDecimal(int val);
//将String转换为BigDecimal
BigDecimal(String val);

但使用double作为构造函数的参数构造BigDecimal对象时,值也会存在不准确的情况。例如:

import java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {
		BigDecimal bd = new BigDecimal(0.2);
		System.out.println("double:" + bd);
    }
}

得到的结果:double:0.200000000000000011102230246251565404236316680908203125。因此我们还是使用String参数的构造函数,使用时只需要Double.toString()转换一下即可。

BigDecimal主要方法

最常用的几个方法如下:

//加法
public BigDecimal add(BigDecimal value);
//减法 
public BigDecimal subtract(BigDecimal value);
//乘法
public BigDecimal multiply(BigDecimal value);
//除法
public BigDecimal divide(BigDecimal value); 
//返回此BigDecimal的绝对值
public BigDecimal abs();
//比较当前BigDecimal与给定的BigDecimal的值,大于则返回1,等于则返回0,小于则返回-1
public int compareTo(BigDecimal val);
//将当前BigDecimal的值转换为double
public double doubleValue();
//将当前BigDecimal转换为float
public float floatValue();
//返回当前BigDecimal与给定的BigDecimal中较大的值,如果相等,返回当前BigDecimal
public BigDecimal max(BigDecimal val);
//返回当前BigDecimal与给定的BigDecimal中较小的值,如果相等,返回当前BigDecimal
public BigDecimal min(BigDecimal val);

除法在不能整除时会报错,但devide()方法有多个重载方法,我们可以调用另一个代替的方法,方法参数如下:

//参数分别为:除数,小数点后保留位数,舍入模式
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode);

其中舍入模式有8种,分别为:

  1. ROUND_UP:向远离零的方向舍入。舍弃非零部分,并将非零舍弃部分相邻的一位数字加一。
  2. ROUND_DOWN:向接近零的方向舍入。舍弃非零部分,同时不会非零舍弃部分相邻的一位数字加一,采取截取行为。
  3. ROUND_CEILING:向正无穷的方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致。注意:此模式不会减少数值大小。
  4. ROUND_FLOOR:向负无穷的方向舍入。如果为正数,舍入结果同ROUND_DOWN一致;如果为负数,舍入结果同ROUND_UP一致。注意:此模式不会增加数值大小。
  5. ROUND_HALF_UP:向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分>= 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。这种模式也就是我们常说的我们的“四舍五入”。
  6. ROUND_HALF_DOWN:向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。如果舍弃部分> 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。这种模式也就是我们常说的我们的“五舍六入”。
  7. ROUND_HALF_EVEN:向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则相邻的偶数舍入。如果舍弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。注意:在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况,如果前一位为奇数,则入位,否则舍去。
  8. ROUND_UNNECESSARY:断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。

需要注意的是,在使用BigDecimal做类似叠加操作时要重新赋值,例如下面的程序叠加操作是无效的。

public class BaseTest {
    public static void main(String[] args) {
        BigDecimal b = new BigDecimal("10");
        for (int i=0;i<10;i++){
            //不正确写法
            b.add(new BigDecimal("1"));
            //正确写法
            //b = b.add(new BigDecimal("1"));
        }
        System.out.println(b);
    }
}

按照以上代码输出结果为10,重新赋值的输出结果为20。原因在于BigDecimal是不可变的,add()方法返回的是一个新的BigDecimal。

<think>好的,用户想知道如何在Java中使用BigDecimal的multiply方法。首先,我需要回忆BigDecimal的基本用法,特别是乘法操作。记得BigDecimal是用来处理高精度计算的,尤其是在金融领域,避免浮点数精度问题。 首先,用户可能需要一个简单的示例,展示multiply的基本使用。比如创建两个BigDecimal对象,然后相乘。要注意构造方法,使用字符串或者valueOf来避免精度丢失,比如引用3中提到的问题,直接用new BigDecimal(0.1)会有精度问题,而用valueOf更好。 然后,用户可能关心如何处理乘法的结果,特别是四舍五入。引用5提到相乘后四舍五入,所以需要介绍MathContext和RoundingMode。比如使用setScale方法设置小数位和舍入模式。 另外,需要注意BigDecimal不可变性,每次运算都会生成新对象。这点在示例中要说明,避免用户误解。 还要考虑异常情况,比如乘数不能为null,否则会抛出NullPointerException。应该提醒用户进行非空判断。 可能还需要提到不同的舍入模式,比如四舍五入、向上取整等,给用户更多的选择。同时,MathContext的使用,比如指定精度和舍入方式,如引用4中提到的乘法方法。 最后,总结使用步骤,强调构造BigDecimal的正确方法、乘法操作、精度控制,以及注意事项,帮助用户避免常见错误。</think>### JavaBigDecimal的乘法操作详解 在Java中,`BigDecimal.multiply()`方法用于实现高精度乘法运算,尤其适用于需要精确计算的场景(如金融计算)[^5]。以下是具体的使用方法和注意事项: --- #### 1. **基础乘法操作** 乘法操作通过`multiply(BigDecimal multiplicand)`实现,返回新的`BigDecimal`对象(BigDecimal不可变类)[^1][^4]。 **示例代码:** ```java BigDecimal num1 = new BigDecimal("10.5"); BigDecimal num2 = new BigDecimal("2.0"); BigDecimal result = num1.multiply(num2); // 结果为21.00 ``` --- #### 2. **控制精度和舍入方式** 乘法结果可能超出预期小数位数,需通过`setScale()`或`MathContext`指定精度和舍入规则。 **示例1:四舍五入保留两位小数** ```java BigDecimal num3 = new BigDecimal("3.1415"); BigDecimal num4 = new BigDecimal("2.0"); BigDecimal result2 = num3.multiply(num4).setScale(2, RoundingMode.HALF_UP); // 结果为6.28 ``` **示例2:使用MathContext限制总精度** ```java MathContext mc = new MathContext(4, RoundingMode.HALF_UP); // 总有效数字4位 BigDecimal result3 = num3.multiply(num4, mc); // 结果为6.283 ``` --- #### 3. **构造BigDecimal的正确方式** 避免使用`double`直接构造对象,优先使用`String`或`BigDecimal.valueOf()`[^3]: ```java // 错误方式:可能丢失精度 BigDecimal bad = new BigDecimal(0.1); // 实际值为0.10000000000000000555... // 正确方式 BigDecimal good1 = new BigDecimal("0.1"); BigDecimal good2 = BigDecimal.valueOf(0.1); // 内部调用Double.toString() ``` --- #### 4. **注意事项** - **空指针检查**:若参数为`null`会抛出`NullPointerException` - **性能优化**:频繁计算时建议重用`MathContext`对象 - **舍入模式选择**:根据场景选择`RoundingMode.UP`(向上取整)、`RoundingMode.DOWN`(直接截断)等[^5] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值