目录
- 简介
- 类介绍
- 案例分析
- 总结
- BigDecimal类型的使用场景
- MySQL中存储BigDecimal类型数据
- 补充: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类型的常见场景:
-
财务计算:在金融或财务领域,需要对金额、利率、汇率等进行精确计算和舍入,避免由于浮点数运算带来的精度问题。
-
税务计算:对于需要计算税额、税率等的场景,BigDecimal类型可以确保计算结果的准确性,并避免误差。
-
计量单位转换:在需要进行单位转换的场景中,BigDecimal可以提供精确的计算结果,确保转换的准确性。
-
程序性能测试:在进行性能测试时,可能需要进行大量的计算操作,使用BigDecimal类型可以避免浮点数运算带来的精度误差,确保测试结果的准确性。
-
商业应用开发:在开发商业应用时,可能需要处理复杂的数值计算,如利润率、销售额等,使用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类型时,有一些注意事项需要注意:
-
避免使用浮点数构造BigDecimal:浮点数在计算机中以二进制表示,可能会导致精度丢失。建议使用字符串或整数构造BigDecimal对象,以确保精确性。
-
使用BigDecimal的字符串构造函数:使用BigDecimal的字符串构造函数可以确保精确性,例如:new BigDecimal(“0.1”)。而使用new BigDecimal(0.1)可能会导致精度丢失。
-
设置精度和舍入模式:可以使用setScale()方法设置BigDecimal的精度和舍入模式。精度指的是小数部分的位数,舍入模式指的是如何处理小数位数超过精度的情况。
-
避免使用equals()方法比较BigDecimal:由于BigDecimal是一个引用类型,使用equals()方法比较BigDecimal对象时,需要确保比较的精确度。推荐使用compareTo()方法来进行比较。
-
避免使用BigDecimal进行大量的运算:由于BigDecimal对象是不可变的,每次进行运算都会创建一个新的BigDecimal对象。如果需要进行大量的计算,可以考虑使用BigDecimal的可变版本MutableBigDecimal。
-
注意处理除法运算的精度:在进行除法运算时,需要注意处理小数位数的精度和舍入模式。可以使用divide()方法指定精度和舍入模式。
总之,使用BigDecimal时需要注意精度、舍入模式和数值的构造方式,以确保计算的准确性和一致性。同时,了解和使用BigDecimal的各种方法,可以更好地处理数值计算和比较。
BigDecimal类型的其他使用
除了上述注意事项,以下是一些BigDecimal类型的其他使用方法:
-
加法和减法:可以使用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
-
乘法和除法:可以使用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
-
取反操作:可以使用negate()方法对BigDecimal对象进行取反操作。例如:
BigDecimal num = new BigDecimal("10.5"); BigDecimal neg = num.negate(); // 结果为-10.5
-
取绝对值:可以使用abs()方法获取BigDecimal对象的绝对值。例如:
BigDecimal num = new BigDecimal("-10.5"); BigDecimal abs = num.abs(); // 结果为10.5
-
比较大小:可以使用compareTo()方法对两个BigDecimal对象进行大小比较。例如:
BigDecimal num1 = new BigDecimal("10.5"); BigDecimal num2 = new BigDecimal("5.3"); int result = num1.compareTo(num2); // 结果为1(num1大于num2)