kt 中商品的金额字段使用double 还是 bigdecimal
在Kotlin中处理商品金额等金融计算时,必须使用 BigDecimal
而非 Double
。以下是核心原因及实践建议:
️ 一、为什么必须用 BigDecimal
?
-
Double
存在精度丢失问题
Double
是二进制浮点数(遵循 IEEE 754 标准),无法精确表示大多数十进制小数(如0.1
)。
示例:println(0.1 + 0.2) // 输出 0.30000000000000004(非精确的 0.3)
这种误差在金额计算中会导致累计错误,尤其涉及乘法、除法时更明显。
-
BigDecimal
的精确性优势- 基于十进制存储,可精确表示任意位数的小数(如
0.1
)。 - 提供对舍入模式(
RoundingMode
)和精度(scale
)的完全控制,确保计算结果符合金融规范。
- 基于十进制存储,可精确表示任意位数的小数(如
✅ 二、Kotlin 中 BigDecimal
的最佳实践
1. 初始化:必须通过字符串构造
// ✅ 正确做法(避免二进制误差)
val price = BigDecimal("19.99")// ❌ 错误做法(直接传 Double 仍会引入误差)
val errorPrice = BigDecimal(19.99) // 实际值可能是 19.990000000000002
2. 运算:显式指定精度和舍入模式
val price = BigDecimal("100.55")
val taxRate = BigDecimal("0.07")// 乘法 + 保留两位小数(四舍五入)
val tax = price.multiply(taxRate).setScale(2, RoundingMode.HALF_UP) // 除法:必须指定精度,否则抛 ArithmeticException
val divided = price.divide(BigDecimal(3), 2, RoundingMode.HALF_UP)
3. 比较数值:用 compareTo()
而非 equals()
val a = BigDecimal("1.0")
val b = BigDecimal("1.00")println(a == b) // false(equals() 严格比较小数位数)
println(a.compareTo(b) == 0) // true(比较实际数值)
三、Double
的适用场景(非金额)
仅适用于科学计算、图形渲染等容忍误差的场景:
// 物理引擎计算(精度要求低)
val velocity = 3.0 * 9.8
🔧 四、封装工具类(推荐)
import java.math.BigDecimal
import java.math.RoundingModeobject MoneyUtils {private const val DEFAULT_SCALE = 2// 安全转换 Double → BigDecimal(字符串中转)fun fromDouble(value: Double): BigDecimal {return BigDecimal(value.toString())}// 金额标准化(保留两位小数)fun normalize(amount: BigDecimal): BigDecimal {return amount.setScale(DEFAULT_SCALE, RoundingMode.HALF_UP)}// 安全除法fun divide(a: BigDecimal, b: BigDecimal, scale: Int = DEFAULT_SCALE): BigDecimal {return a.divide(b, scale, RoundingMode.HALF_UP)}
}// 使用示例
val total = MoneyUtils.fromDouble(100.0).multiply(BigDecimal("0.85")).let { MoneyUtils.normalize(it) }
💾 五、数据库存储方案
方案 | 数据类型 | Java/Kotlin 类型 | 优点 | 缺点 |
---|---|---|---|---|
推荐 ✅ | DECIMAL(18,2) | BigDecimal | 精确存储,代码直观 | 占用空间略大 |
高性能场景(分单位) | BIGINT | Long | 存储小,查询快 | 需手动转换元/分单位 |
📌 总结
- 金额字段必须用
BigDecimal
:避免Double
的二进制精度陷阱。 - 通过字符串初始化:
BigDecimal("123.45")
是唯一安全方式。 - 显式控制精度和舍入:所有运算(尤其除法)需指定
scale
和RoundingMode
。 - 数据库存储匹配:使用
DECIMAL(p,s)
类型直接映射BigDecimal
。
在金融系统中,精度错误可能导致资损或法律纠纷,务必严格遵循上述规范。