java中存储金额字段

java中存储金额字段

商场或者电商项目中会涉及到金额字段的存储,本文来讨论下选择最多的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)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值