金额计算为什么不能用 double?BigDecimal 内部是什么、有哪些容易踩的坑?这篇一项项打印出来(new BigDecimal(0.1) 的精度陷阱已有专门文章,这里补充 scale 规则、equals 与舍入)。
double:0.1 + 0.2 = 0.30000000000000004;0.1 + 0.2 == 0.3 是 false;0.1f + 0.2f == 0.3f 是 true
BigDecimal 的三种构造:
new BigDecimal(0.1) = 0.1000000000000000055511151231257827021181583404541015625
BigDecimal.valueOf(0.1) = 0.1(用 Double.toString)
new BigDecimal("0.1") = 0.1
new BigDecimal("0.1").add(new BigDecimal("0.2")) = 0.3,compareTo(0.3) = 0x = 12.50(unscaled=1250,scale=2,precision=4),y = 0.125(unscaled=125,scale=3,precision=3) x + y = 12.625(unscaled=12625,scale=3,precision=5)(scale 取较大的) x - y = 12.375(unscaled=12375,scale=3,precision=5) x × y = 1.56250(unscaled=156250,scale=5,precision=6)(scale 相加:2 + 3) 1 / 3 → ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 1 / 8 = 0.125(unscaled=125,scale=3,precision=3)(能除尽就精确);1 / 3,scale=5,HALF_UP = 0.33333;MathContext(5) = 0.33333
2.0 equals 2.00 = false;compareTo = 0(equals 连 scale 一起比较) HashSet 里放 2.0,contains(2.00) = false;TreeSet(用 compareTo)放 2.0,contains(2.00) = true hashCode:2.0 → 621,2.00 → 6202 stripTrailingZeros:2.00 → 2;100 → 1E+2(toString 变成科学计数法),toPlainString = 100
把 2.5、3.5、-2.5、2.51 setScale(0,…),各种舍入模式的结果:
2.5 3.5 -2.5 2.51 2.49
HALF_UP 3 4 -3 3 2
HALF_EVEN 2 4 -2 3 2
HALF_DOWN 2 3 -2 3 2
UP 3 4 -3 3 3
DOWN 2 3 -2 2 2
CEILING 3 4 -2 3 3
FLOOR 2 3 -3 2 2
RoundingMode.UNNECESSARY 在需要舍入时抛 ArithmeticException: Rounding necessary
先把每个 0.333 舍入到 2 位再累加 3 次 = 0.99;先累加再舍入 = 1.00(舍入的时机会影响结果)