我们首先运行下面这段程序:
public static void main(String[] args) {
System.out.println("1.023 + 2.29 = " + (1.023 + 2.29));
System.out.println("3.82 - 1.572 = " + (3.82 - 1.572));
System.out.println("2.9 * 45.3 = " + (2.9 * 45.3));
System.out.println("9.24 / 3.81 = " + (9.24 / 3.81));
}
结果为:
1.023 + 2.29 = 3.3129999999999997
3.82 - 1.572 = 2.2479999999999998
2.9 * 45.3 = 131.36999999999998
9.24 / 3.81 = 2.425196850393701
java中的简单浮点数类型float和double不能够进行运算,因为计算机是二进制的,从二进制转化为十进制浮点数时,精度容易丢失,导致精度下降,此问题会造成严重的后果。商业计算中我们要用java.math.BigDecimal,通过使用BigDecimal类可以解决上述问题,但一定要用BigDecimal(String)构造器,而千万不要用BigDecimal(double)来构造。
剖析精度丢失的原因 – 小数二进制表示问题
1)十进制整数转化二进制
eg: 13转化为二进制 (除2取余,逆序排列)
二进制表示为:(自下而上)1101
所有的整数除以2最终一定会等于0,不会无限循环下去,但是小数呢?
2)十进制小数转化为二进制
eg: 0.9转化为二进制(乘2取整,顺序排列)
二进制表示为: (自上而下)0.1110011001100…
计算结果循环了,将无限计算下去,所以,小数的二进制有时是不能精确的。
BigDecimal实现代码
import java.math.BigDecimal;
public class ArithUtils {
// 默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
// 工具类不能实例化
private ArithUtils() {
}
/**
* 提供精确的加法运算。
*
* @param d1 被加数
* @param d2 加数
* @return 两个参数的和
*/
public static double add(double d1, double d2) {
BigDecimal b1 = new BigDecimal(Double.toString(d1));
BigDecimal b2 = new BigDecimal(Double.toString(d2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param d1 被减数
* @param d2 减数
* @return 两个参数的差
*/
public static double sub(double d1, double d2) {
BigDecimal b1 = new BigDecimal(Double.toString(d1));
BigDecimal b2 = new BigDecimal(Double.toString(d2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
*
* @param d1 被乘数
* @param d2 乘数
* @return 两个参数的积
*/
public static double mul(double d1, double d2) {
BigDecimal b1 = new BigDecimal(Double.toString(d1));
BigDecimal b2 = new BigDecimal(Double.toString(d2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
*
* @param d1 被除数
* @param d2 除数
* @return 两个参数的商
*/
public static double div(double d1, double d2) {
return div(d1, d2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指定精度,以后的数字四舍五入。
*
* @param d1 被除数
* @param d2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double d1, double d2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(d1));
BigDecimal b2 = new BigDecimal(Double.toString(d2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
*
* @param d 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double d, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(d));
BigDecimal one = new BigDecimal("1");
return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
public static void main(String[] args) {
System.out.println("1.023 + 2.29 = " + ArithUtils.add(1.023, 2.29));
System.out.println("3.82 - 1.572 = " + ArithUtils.sub(3.82, 1.572));
System.out.println("2.9 * 45.3 = " + ArithUtils.mul(2.9, 45.3));
System.out.println("9.24 / 3.81 = " + ArithUtils.div(9.24, 3.81));
System.out.println("100.2355436保留小数点后三位:" + ArithUtils.round(100.2355436, 3));
}
}
运算结果:
1.023 + 2.29 = 3.313
3.82 - 1.572 = 2.248
2.9 * 45.3 = 131.37
9.24 / 3.81 = 2.4251968504
100.2355436保留小数点后三位:100.236