一、是什么
BigDecimal是java提供的一个不变的、任意精度的有符号十进制数对象。它可以用来处理更大或更小的数,进行精确的数值运算。
1.1. 作用
- 高精度的数学运算:由于BigDecimal支持任意精度的加、减、乘、除等数学运算,因此可以避免使用doouble或float类型是出现的精度丢失问题。
- 金融计算:金融计算对精度要求很高,使用BigDecimal可以保证计算结果的准确性。
- 数字格式化:BigDecimal类可以将数字格式化成各种格式,如货币格式、百分比格式等。
- 数据库操作:在处理数据库的数值类型时,使用BigDecimal可以避免精度丢失。
1.2. 为什么要用
- 精度要求:BigDecimal提供了更高的精度计算,可以避免使用float或double类型时可能出现的精度丢失或计算错误的问题。这对于金融计算、货币计算等需要精确结果的场景尤其重要。
- 性能考虑:虽然BigDecimal在某些情况下会比使用float或double类型消耗更多的内存和CPU资源,但是在需要高精度计算的场景中,其性能优势是无可比拟的。由于其内部采用字符串存储和运算,因此可以避免浮点数运算中的舍入误差和精度限制。
- 稳定性:BigDecimal的内部实现是经过精心设计和优化的,可以确保在各种场景下的稳定性和可靠性。此外,由于其采用字符串存储方式,可以避免由于数据溢出、舍入误差等问题导致的程序崩溃或数据丢失等情况。
- 兼容性:BigDecimal与其他Java库的兼容性较好,可以方便地进行数值计算和数据处理。同时,由于其内部采用BigInteger进行运算,可以方便地进行大数计算和处理。
二、精度丢失示例代码
示例代码:
public class test {
public static void main(String[] args) {
System.out.println(0.2 + 0.1);
System.out.println(0.3 - 0.1);
System.out.println(0.2 * 0.1);
System.out.println(0.3 / 0.1);
}
}
输出结果:
结论:以上得到的结果与预期的不一致。在计算机科学中,浮点数(double/float)不能精确的表示所有小数,是因为浮点数是以二进制的形式表示的,而一些十进制小数不能用有限的二进制小数精确表示。
例如,0.1(十进制)不能被精确地表示为二进制小数。当你进行浮点数运算时,如加、减、乘、除等,由于这种精度问题,可能会导致结果与预期不符。
三、BigDecimal构造方法
- BIgDecimal(double val):将double表示形式转换为BigDecimal,*不建议使用
- BIgDecimal(int val):将int表示形式转换为BigDecimal
- BIgDecimal(String val):将String表示形式转换为BigDecimal
示例代码:
public class test {
public static void main(String[] args) {
BigDecimal bInt = new BigDecimal(2); //参数int
BigDecimal bDouble = new BigDecimal(2.3); //参数double
BigDecimal bString = new BigDecimal("2.3"); //参数String
System.out.println("bInt=" + bInt);
System.out.println("bDouble=" + bDouble);
System.out.println("bString=" + bString);
}
}
输出结果:
以上示例代码,使用double作为参数创建BigDecimal对象时,遇到了精度丢失的问题。当使用double值作为构造参数时,由于二进制无法精确表示某些十进制小数(例如,二进制中的0.1等于十进制中的0.4,而0.2等于十进制的0.5,因此二进制无法精确表示0.1+0.2=0.3),这可能导致一些精度丢失。
可以尝试使用BigDecimal.valueOf(2.3)方法创建BigDecimal对象,该方法可以避免精度丢失:
public class test {
public static void main(String[] args) {
BigDecimal bDouble = BigDecimal.valueOf(2.3);
System.out.println(bDouble);
}
}
输出结果:
四、加减乘除运算
4.1. 示例代码(加减乘除)
public class test {
public static void main(String[] args) {
// 创建两个BigDecimal对象
BigDecimal num1 = new BigDecimal("10.5");
BigDecimal num2 = new BigDecimal("1.3");
// 加法
BigDecimal sum = num1.add(num2);
System.out.println("加法结果: " + sum);
// 减法
BigDecimal difference = num1.subtract(num2);
System.out.println("减法结果: " + difference);
// 乘法
BigDecimal product = num1.multiply(num2);
System.out.println("乘法结果: " + product);
// 除法
BigDecimal quotient = num1.divide(num2, 2, RoundingMode.HALF_UP); // 设置精度为2位小数,使用四舍五入
System.out.println("除法结果: " + quotient);
}
}
输出结果:
注意:BigDecimal除法(divide)可能出现不能整除的情况,比如10.5/1.3,这时会报以下错误:
但是divide方法有三个参数可以解决:
- BIgDecimal divisor:第一个参数表示除数;
- int scale:第二个参数表示小数点后保留位数;
- int roundingMode:第三个参数表示舍入模式。
只有在作除法运算或四舍五入时才用到舍入模式,有以下几种:
- RoundingMode.DOWN:向下舍入模式。无论小数部分是否为零,都直接舍去。例如,10.5 / 3.2的结果为3.309375,但使用向下舍入模式后结果为3。
- RoundingMode.UP:向上舍入模式。无论小数部分是否为零,都直接进位。例如,10.5 / 3.2的结果为3.309375,但使用向上舍入模式后结果为4。
- RoundingMode.HALF_UP:四舍五入模式。当小数部分大于等于0.5时进位,小于0.5时舍去。例如,10.5 / 3.2的结果为3.309375,但使用四舍五入模式后结果为3.31。
- RoundingMode.HALF_DOWN:四舍五入模式(向下)。当小数部分大于等于0.5时舍去,小于0.5时进位。例如,10.5 / 3.2的结果为3.309375,但使用四舍五入模式(向下)后结果为3.30。
- RoundingMode.HALF_EVEN:四舍六入五成双模式。当小数部分大于等于0.5且小于1时进位,其他情况下舍去。例如,10.5 / 3.2的结果为3.309375,但使用四舍六入五成双模式后结果为3.31。也称为“银行家舍入法”,优点是在重复进行一系列计算时,可以将累加错误减到最小。
。。。
4.2. 四舍五入/截断
示例代码:
public class test {
public static void main(String[] args) {
BigDecimal a = new BigDecimal("4.5635");
a = a.setScale(3, RoundingMode.HALF_UP); //保留3位小数,且四舍五入
System.out.println(a);
}
}
输出结果:
处理BigDecimal使用setScale方法操作。
五、比较大小
5.1. compareTo
public class test {
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal(61.40);
System.out.println("b1小数位数" + b1.scale());
BigDecimal b2 = new BigDecimal(61.4);
System.out.println("b2小数位数" + b2.scale());
BigDecimal b3 = new BigDecimal("61.40");
System.out.println("b3小数位数" + b3.scale());
BigDecimal b4 = new BigDecimal("61.4");
System.out.println("b4小数位数" + b4.scale());
System.out.println("===============compareTo==================");
System.out.println("61.40 数值和61.4数值比较 " + b1.compareTo(b2));
System.out.println("61.40 数值和61.40字符比较 " + b1.compareTo(b3));
System.out.println("61.40 字符和61.4字符比较 " + b3.compareTo(b4));
}
}
�输出结果:
5.2. equals
示例代码:
public class test {
public static void main(String[] args) {
BigDecimal b1 = new BigDecimal(61.40);
System.out.println("b1小数位数" + b1.scale());
BigDecimal b2 = new BigDecimal(61.4);
System.out.println("b2小数位数" + b2.scale());
BigDecimal b3 = new BigDecimal("61.40");
System.out.println("b3小数位数" + b3.scale());
BigDecimal b4 = new BigDecimal("61.4");
System.out.println("b4小数位数" + b4.scale());
System.out.println("===============equals==================");
System.out.println("61.40 数值和61.4数值比较 " + b1.equals(b2));
System.out.println("61.40 数值和61.40字符比较 " + b1.equals(b3));
System.out.println("61.40 字符和61.4字符比较 " + b3.equals(b4));
}
}
输出结果:
5.3. compareTo和equals区别
equals方法会比较两部分内容,分别是值(value)和精度(scale,即小数位数);
compareTo方法比较会忽略精度;
equals代码中比较逻辑(可以看到不仅比较了值还比较了精度(及小数位数))
compareTo代码中比较逻辑(精度相同和不同都做了比较):
六、BigDecimal转String
示例代码:
public class test {
public static void main(String[] args) {
// 浮点数的打印
System.out.println(new BigDecimal("10000000000").toString());
// 普通的数字字符串
System.out.println(new BigDecimal("100.000").toString());
// 去除末尾多余的0
System.out.println(new BigDecimal("100.000").stripTrailingZeros().toString());
// 避免输出科学计数法
System.out.println(new BigDecimal("100.000").stripTrailingZeros().toPlainString());
}
}
输出结果:
以上结果原因:
stripTrailingZeros()方法是去除末尾的零;toPlainString()方法将结果转换为普通的字符串表示,而不是科学计数法。,因此使用toString()会得到科学记数(1E+2)。
七、总结
Java中的BigDecimal类是用于高精度计算的类,它可以表示非常大或非常小的浮点数,并且可以避免由于浮点数精度限制而导致的问题。
7.1. 创建BigDecimal对象
- 使用字符串创建:BigDecimal bd = new BigDecimal(“123.456”);
- 使用双精度浮点数创建:BigDecimal bd = new BigDecimal(123.456); *不推荐使用
- 使用整数创建:BigDecimal bd = new BigDecimal(123);
7.2. 四舍五入
- 使用setScale()方法进行四舍五入:bd = bd.setScale(2, RoundingMode.HALF_UP);
7.3. 舍去末尾零
- 使用stripTrailingZeros()方法:bd = bd.stripTrailingZeros();
7.4. 比较
- 使用compareTo()方法进行比较:int result = bd1.compareTo(bd2);
- 使用equals()方法进行相等性比较:boolean isEqual = bd1.equals(bd2);
7.5. 算数运算
- 加法:bd = bd1.add(bd2);
- 减法:bd = bd1.subtract(bd2);
- 乘法:bd = bd1.multiply(bd2);
- 除法:bd = bd1.divide(bd2, BigDecimal.ROUND_HALF_UP);
7.6. 数学函数
- 平方根:bd = bd.sqrt();
- 指数:bd = bd.pow(2);
- 对数:bd = bd.log();
7.7. 格式化输出
- 使用toPlainString()方法返回普通字符串表示形式。
- 使用toString()方法返回字符串表示形式。
- 使用toEngineeringString()方法返回工程字符串表示形式。
7.8. 转换为其它数值类型
- 转换为长整型:long l = bd.longValue();
- 转换为双精度浮点数:double d = bd.doubleValue();
7.9. 注意事项
- 在进行除法运算时,如果除数为0,会抛出ArithmeticException异常。
- 在进行除法运算时,如果结果为无限大或无限小,也会抛出异常。
- 使用BigDecimal进行高精度计算时,应该避免直接使用浮点数或双精度浮点数进行计算,因为它们可能存在精度问题。