You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Protobuf定义文件中传输任意精度小数,应使用哪种正确scalar type?

金融场景下gRPC Protobuf序列化decimal值的选型分析

这确实是金融系统里很常见的序列化选型困惑,我来帮你拆解下每个选项的优劣势,以及适配Go+shopspring/decimal的最佳实践:

1. 绝对不推荐:double类型

虽然double对应Go的float64,但金融场景下绝对要避开——哪怕只是序列化环节也不行。原因很简单:浮点数的二进制存储本身就存在精度丢失问题,比如0.1这样的十进制小数无法被二进制浮点数精确表示。哪怕你在业务代码里用shopspring/decimal保证了计算精度,一旦序列化成double再反序列化,就可能引入不可逆的精度误差,这对金融数据来说是致命的。

2. 稳妥但繁琐:string类型

用string存储decimal值是完全安全的,因为shopspring/decimal本身就支持从字符串解析(decimal.NewFromString())和转成字符串输出(decimal.String()),精度100%有保障。

但缺点也很明显:

  • 序列化后的数据体积会比数值类型大一些
  • 其他语言的客户端需要额外做字符串转decimal的处理,增加了跨语言协作的成本
  • Protobuf的校验规则(比如范围校验)没法直接作用在字符串上,需要自己实现业务层校验

3. 更优解:自定义Protobuf Decimal类型

类似mgravell/protobuf-net里的decimal类型,你可以在Protobuf里定义一个自定义的decimal消息类型,来映射shopspring/decimal的内部结构——毕竟shopspring/decimal本质上是由整数系数和缩放因子组成的(比如123.45会被存成系数12345,缩放因子2)。

举个例子,定义Protobuf类型:

message Decimal {
  // 对应shopspring/decimal的Coefficient(整数部分,可能为负)
  int64 coefficient = 1;
  // 对应缩放因子,即小数点后的位数
  int32 scale = 2;
}

然后在Go代码里写一个转换工具函数:

import (
    "math/big"
    "github.com/shopspring/decimal"
    pb "your/proto/package/path"
)

// 将shopspring/decimal转成自定义Protobuf Decimal
func DecimalToProto(d decimal.Decimal) *pb.Decimal {
    return &pb.Decimal{
        Coefficient: d.Coefficient().Int64(),
        Scale:       int32(d.Scale()),
    }
}

// 将Protobuf Decimal转成shopspring/decimal
func ProtoToDecimal(p *pb.Decimal) decimal.Decimal {
    return decimal.NewFromBigInt(big.NewInt(p.Coefficient), p.Scale)
}

这种方式的优势:

  • 完全保留精度,和shopspring/decimal的内部逻辑对齐
  • 序列化后的数据体积小,效率高
  • 跨语言客户端可以很容易地映射到各自语言的decimal类型(比如Java的java.math.BigDecimal、C#的decimal)
  • 可以在Protobuf里给coefficient和scale加校验规则(比如scale不能为负)

总结建议

如果你的服务是纯Go生态,且对开发效率要求极高,string类型可以凑合用;但如果是跨语言的gRPC服务,或者追求最佳的精度和性能,自定义Protobuf Decimal类型是最优选择。千万不要为了省事用double,金融场景下的精度误差是不可接受的。

内容的提问来源于stack exchange,提问作者Matt Mc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:20:02