我们首先运行下面这段程序:

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取余,逆序排列)

Java 校验小数点后位数 java小数精度问题_四舍五入


二进制表示为:(自下而上)1101

所有的整数除以2最终一定会等于0,不会无限循环下去,但是小数呢?

2)十进制小数转化为二进制

eg: 0.9转化为二进制(乘2取整,顺序排列)

Java 校验小数点后位数 java小数精度问题_System_02


二进制表示为: (自上而下)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