BigDecimal 介绍
BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal 来做的。
纳尼,浮点数的运算竟然还会有精度丢失的风险吗?确实会!
示例代码:
1 2 3 4 5
| float a = 2.0f - 1.9f; float b = 1.8f - 1.7f; System.out.println(a); System.out.println(b); System.out.println(a == b);
|
为什么浮点数 float 或 double 运算的时候会有精度丢失的风险呢?
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
1 2 3 4 5 6 7 8
|
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(发生循环) ...
|
BigDecimal 的用处
《阿里巴巴 Java 开发手册》中提到:浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。

具体原因我们在上面已经详细介绍了,这里就不多提了,我们下面直接上实例:
1 2 3 4 5
| float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; System.out.println(a); System.out.println(b); System.out.println(a == b);
|
从输出结果就可以看出发生精度丢失的问题。
想要解决这个问题也很简单,直接使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作即可。
1 2 3 4 5 6 7 8 9 10
| BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b); BigDecimal y = b.subtract(c);
System.out.println(x); System.out.println(y); System.out.println(Objects.equals(x, y));
|
BigDecimal 常见方法
加减乘除
add 方法用于将两个 BigDecimal 对象相加,subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。
1 2 3 4 5 6 7
| BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); System.out.println(a.add(b)); System.out.println(a.subtract(b)); System.out.println(a.multiply(b)); System.out.println(a.divide(b)); System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));
|
这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数版本,并且RoundingMode 不要选择 UNNECESSARY,否则很可能会遇到 ArithmeticException(无法除尽出现无限循环小数的时候),其中 scale 表示要保留几位小数,roundingMode 代表保留规则。
1 2 3
| public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) { return divide(divisor, scale, roundingMode.oldMode); }
|
保留规则非常多,这里列举几种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public enum RoundingMode { UP(BigDecimal.ROUND_UP), DOWN(BigDecimal.ROUND_DOWN), CEILING(BigDecimal.ROUND_CEILING), FLOOR(BigDecimal.ROUND_FLOOR), HALF_UP(BigDecimal.ROUND_HALF_UP), }
|
大小比较
a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b。
1 2 3
| BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); System.out.println(a.compareTo(b));
|
保留几位小数
通过 setScale方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。
1 2 3
| BigDecimal m = new BigDecimal("1.255433"); BigDecimal n = m.setScale(3,RoundingMode.HALF_DOWN); System.out.println(n);
|
BigDecimal 的使用注意事项
注意:我们在使用 BigDecimal 时,为了防止精度丢失,推荐使用它的BigDecimal(String val)构造方法或者 BigDecimal.valueOf(double val) 静态方法来创建对象。
《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。

BigDecimal 工具类分享
网上有一个使用人数比较多的 BigDecimal 工具类,提供了多个静态方法来简化 BigDecimal 的操作。
我对其进行了简单改进,分享一下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
| import java.math.BigDecimal; import java.math.RoundingMode;
public class BigDecimalUtil {
private static final int DEF_DIV_SCALE = 10;
private BigDecimalUtil() { }
public static double add(double v1, double v2) { BigDecimal b1 = BigDecimal.valueOf(v1); BigDecimal b2 = BigDecimal.valueOf(v2); return b1.add(b2).doubleValue(); }
public static double subtract(double v1, double v2) { BigDecimal b1 = BigDecimal.valueOf(v1); BigDecimal b2 = BigDecimal.valueOf(v2); return b1.subtract(b2).doubleValue(); }
public static double multiply(double v1, double v2) { BigDecimal b1 = BigDecimal.valueOf(v1); BigDecimal b2 = BigDecimal.valueOf(v2); return b1.multiply(b2).doubleValue(); }
public static double divide(double v1, double v2) { return divide(v1, v2, DEF_DIV_SCALE); }
public static double divide(double v1, double v2, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b1 = BigDecimal.valueOf(v1); BigDecimal b2 = BigDecimal.valueOf(v2); return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); }
public static double round(double v, int scale) { if (scale < 0) { throw new IllegalArgumentException( "The scale must be a positive integer or zero"); } BigDecimal b = BigDecimal.valueOf(v); BigDecimal one = new BigDecimal("1"); return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue(); }
public static float convertToFloat(double v) { BigDecimal b = new BigDecimal(v); return b.floatValue(); }
public static int convertsToInt(double v) { BigDecimal b = new BigDecimal(v); return b.intValue(); }
public static long convertsToLong(double v) { BigDecimal b = new BigDecimal(v); return b.longValue(); }
public static double returnMax(double v1, double v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.max(b2).doubleValue(); }
public static double returnMin(double v1, double v2) { BigDecimal b1 = new BigDecimal(v1); BigDecimal b2 = new BigDecimal(v2); return b1.min(b2).doubleValue(); }
public static int compareTo(double v1, double v2) { BigDecimal b1 = BigDecimal.valueOf(v1); BigDecimal b2 = BigDecimal.valueOf(v2); return b1.compareTo(b2); }
}
|
总结
浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。
不过,Java 提供了BigDecimal 来操作浮点数。BigDecimal 的实现利用到了 BigInteger (用来操作大整数), 所不同的是 BigDecimal 加入了小数位的概念。