BigDemical金额字段详解

商场或者电商项目中会涉及到金额类型字段的存储,本文来讨论下选择最多的BigDecimal保证精度的原理以及其他字段类型替换方案

BigDemical

BigDecimal 是处理高精度数值计算的核心类,底层实现通过 整数未缩放值标度的组合来保证精确的十进制数值表示。

底层数据结构

BigDecimal 的数值由两个关键部分组成:

  1. 未缩放值(unscaledValue
    • 类型为 BigInteger,表示所有有效数字(不含小数点)。
    • 例如:123.45unscaledValue = 12345
  2. 标度(scale
    • 类型为 int,表示小数点后的位数(即数值 = unscaledValue × 10^-scale)。
    • 例如:123.45scale = 2

若标度为负数,表示小数点前补零的数量:

  • 例如:12345scale = -3)→ 12345 × 10^3 = 12,345,000

保证精度的原理

  1. 基于十进制的精准存储
  • BigDecimal 直接存储十进制数字的每一位,而非二进制浮点数的近似值(如 double 的 IEEE 754 格式)。
  • 例如:0.1BigDecimal 中被精确存储为 unscaledValue=1scale=1,而 double 会存储为 0.10000000000000000555...
  1. 运算时保留所有中间精度
  • 所有算术运算(如加、减、乘、除)均基于 unscaledValuescale 的精确操作,不会丢失有效数字
  • 例如:加法会先对齐标度(扩展数值到相同 scale),再进行整数运算。
  1. 显式控制舍入行为

    • 对于除法用户必须指定舍入模式(RoundingMode精度(小数位数),避免隐式截断。
    • 例如:10 ÷ 3 = 3.333...,但通过 divide(scale, RoundingMode) 可得到 3.33scale=2)。

基于特殊结构的运算

1. 加法

  • 对齐标度:将两个操作数的 scale 调整为相同值(取较大值),扩展 unscaledValue

  • 整数相加:对扩展后的 unscaledValue 执行加法。

    BigDecimal a = new BigDecimal("1.23");  // unscaled=123, scale=2
    BigDecimal b = new BigDecimal("4.5");   // unscaled=45,  scale=1
    // 对齐标度到 2 → b 扩展为 450 (scale=2)
    // 123 + 450 = 573 → 5.73
    

2. 乘法

  • 直接相乘:两个 unscaledValue 相乘,标度相加。

    BigDecimal a = new BigDecimal("1.2");  // unscaled=12, scale=1
    BigDecimal b = new BigDecimal("0.3");  // unscaled=3,  scale=1
    // unscaled=12 * 3=36, scale=1+1=2 → 0.36
    

3. 除法

  • 扩展分子:通过增加 unscaledValue 的标度,避免精度丢失。

  • 整数除法:使用 BigInteger 的除法算法,结合舍入模式截断余数。

    BigDecimal a = new BigDecimal("10");   // unscaled=10, scale=0
    BigDecimal b = new BigDecimal("3");    // unscaled=3,  scale=0
    // 10 ÷ 3 → 标度扩展为 2 → 1000 ÷ 3 = 333 (余1)
    // 应用 RoundingMode.HALF_UP → 3.33
    

使用时注意的点

1. 构造方法

  • 需要传入字符串而不是浮点数

    new BigDecimal("0.1")
    BigDecimal.valueOf(0.1)//底层调用 Double.toString()
    

2. 未指定舍入模式的除法

  • 错误写法:

    a.divide(b)
    
    • 若结果为无限小数(如 1/3),会抛出 ArithmeticException
  • 正确写法:

    a.divide(b, scale, RoundingMode.HALF_UP)
    
    • 显式指定精度和舍入模式。

3. 比较操作的陷阱

  • 错误写法:

    a.equals(b)
    
    • 同时检查 unscaledValuescale2.02.00 不相等。
  • 正确写法:

    a.compareTo(b) == 0
    
    • 仅比较数值大小,忽略标度差异。

Long(BigInteger)

将金额转换为最小货币单位(比如 分),然后用整数进行存储,这样就可以避免浮点数运算导致的精度缺失,比如

  • 1.23元-->123分

  • 100.50元-->10050分

    long amountInCents = 123; // 1.23 元
    // 转换为字符串显示
    String display = String.format("%d.%02d", amountInCents / 100, amountInCents % 100);
    

优点

  • 不存在浮点数的进度丢失问题
  • 基本数据类型运算速度更快,内存占用也更小

缺点

  • 需要处理单位转换,每次展示数据都要/100
  • 范围限制,long的最大值为9,223,372,036,854,775,807,约为900万亿亿,如果还有更大的范围们可以转为BigInteger拓展

由上可知金额字段类型的选择原则:

  • 避免 double/float:二进制浮点类型会导致精度丢失(如 0.1 无法精确表示)。
  • 优先选择不可变类型:确保金额在计算中不会被意外修改(如 BigDecimallong)。
全部评论

相关推荐

牛客501015981号:前面志愿结束了,在大池子里面被其他部门捞了
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务