bigywater.tech
Java · 173

BigDecimal:金额计算为什么会出现 0.1 + 0.2

金额计算为什么不能用 double?BigDecimal 内部是什么、有哪些容易踩的坑?这篇一项项打印出来(new BigDecimal(0.1) 的精度陷阱已有专门文章,这里补充 scale 规则、equals 与舍入)。

0.30000000000000004
double 的 0.1 + 0.2
2.0 ≠ 2.00
BigDecimal.equals 比较 scale,compareTo 为 0
0.99 vs 1.00
先舍入再累加 vs 先累加再舍入

为什么不能用 double

实测 · 本机执行输出
  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) = 0

scale 规则

实测 · 本机执行输出
  x = 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

equals 与 compareTo

实测 · 本机执行输出
  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(舍入的时机会影响结果)

分步演示

BigDecimal
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12
    • 没有验证:BigDecimal 的性能与内存、大数值的运算、MathContext 的 precision 与 scale 的区别细节、DecimalFormat 与 NumberFormat 的格式化、数据库 DECIMAL 的对应;JDK 17 与 27 的 hashCode 数值
    京ICP备2026067502号-1京公网安备11010502063487号