rust_decimal 读取 PostgreSQL 的 1e32 会 panic,Result 为什么没拦住
PostgreSQL NUMERIC 能保存的数可能超出 rust_decimal 的 96 位系数范围。1.43.0 的大权重解码分支仍可能在构造中 panic;需要保留大值时先读取文本,再走可返回错误的解析或选择足够精度的模型。
数据库中 1e32::numeric 是有效值,Rust 客户端读取为 rust_decimal::Decimal 时却可能触发:
Number exceeds maximum value that can be represented.
如果原本期待驱动通过 Result 返回转换错误,要检查 rust_decimal 的 PostgreSQL 解码分支。正式 1.43.0 对部分大权重数值仍可能在内部构造 Decimal 时 panic,外层普通的 ? 不会把 panic 自动变成 Err。
i128 能容纳,不代表 Decimal 能容纳
PostgreSQL NUMERIC 的范围远大于 rust_decimal 的 96 位十进制系数范围。二进制协议以每组四位十进制数和 weight 表示数值;像 1e32 这样的值可以只有一个有效数字组,其余位由权重表示。
1.43.0 先用 i128 的 checked_pow 计算放大因子,再调用 Decimal::from_i128_with_scale。10 的 32 次方、36 次方能通过 i128 的检查,却已经超过 Decimal 系数上限;后面的构造因此仍可能 panic。前面出现 checked 并不代表整个链条都通过可恢复错误返回。
这条共同转换路径也被相关 PostgreSQL 驱动集成使用。应核对应用实际启用的 feature、驱动与查询结果类型,不能把所有 NUMERIC 读取失败都认作相同分支。
需要保留原值时,先避开二进制 Decimal 解码
对于可能超出 Decimal 范围的列,可以在明确的查询入口先取得文本:
SELECT amount::text AS amount_text
FROM ledger
WHERE id = $1;
这里假定 ledger、amount 和参数位置来自应用已有查询。客户端应把 amount_text 读取为字符串,而不是继续声明为 Decimal,否则没有避开目标类型转换。
如果业务仍要求 Decimal,可在拿到文本后显式使用可返回错误的精确解析:
use rust_decimal::Decimal;
fn parse_amount(text: &str) -> Result<Decimal, rust_decimal::Error> {
Decimal::from_str_exact(text)
}
范围或精度不足时,把错误交回既有的业务校验,不返回零、不截掉整数位。文本仍可以保存为诊断原件;如果业务确实允许更大的精确数,应选择能够承接该范围的数值模型,并评估驱动映射及序列化契约。
不要先把 NUMERIC 转成 double precision 或 f64 再绕回 Decimal。浮点数可能在 panic 消失之前就已经丢失有效位,尤其不适合以“不报错”作为金额或计数处理的唯一标准。
数据库限制与客户端修复分开处理
若列按业务本来就只允许较小范围,可以另行审查写入校验和数据库约束。但不能为了客户端读得下,直接缩窄已有列或批量舍入历史值;那是数据规则变更,需要先核对现存数据与消费方。
截至 2026 年 10 月 25 日,正式 1.43.0 的相关分支仍使用上述构造。后续更新时,应核对所选正式实现是否改为可失败构造,并用相同的大值输入验证错误出口。
验证普通数、业务边界、1e32/1e36、负大值,以及业务可能遇到的特殊 NUMERIC 表示。正常值保持精度,超范围输入走明确错误或足够精度的类型,不再依赖 panic 展开。整个查询的行级或事务失败处理也应沿用原约定,避免部分结果被误记为全部成功。


