从这个问题开始,在协议缓冲区中表示 System.Decimal 对象的最佳方式是什么?
5 回答
好吧,protobuf-net 会为你简单地处理这个;它运行类型的属性,并完全支持decimal
. 由于在 proto 中没有直接的表达方式decimal
,它不会(当前)decimal
从“.proto”文件生成属性,但识别一些常见类型(“BCL.Decimal”或类似的)将是一个很好的调整并将其解释为十进制。
至于表示它——我在 protobuf-net wiki 区域有一个关于这个的讨论文件(我怀疑现在已经过时了);现在 protobuf-net 中有一个工作版本,可以为您简单地完成它。
毫无疑问,乔恩和我将在今天晚些时候更进一步地解决这个问题;-p
这个(在 .proto 中)的 protobuf-net 版本类似于(从这里):
message Decimal {
optional uint64 lo = 1; // the first 64 bits of the underlying value
optional uint32 hi = 2; // the last 32 bis of the underlying value
optional sint32 signScale = 3; // the number of decimal digits, and the sign
}
Marc 和我有一个非常模糊的计划来提出一个“通用 PB 消息”库,这样您就可以以一种通用的方式表示非常常见的类型(日期/时间和小数),并且可以在 .NET 和 Java 中进行转换(以及任何其他人想要贡献的东西)。
如果您乐于坚持使用 .NET,并且正在寻找紧凑性,我可能会选择以下内容:
message Decimal {
// 96-bit mantissa broken into two chunks
optional uint64 mantissa_msb = 1;
optional uint32 mantissa_lsb = 2;
required sint32 exponent_and_sign = 3;
}
符号只能用 exponent_and_sign 的符号表示,指数为绝对值。
使尾数的两个部分都是可选的意味着 0 表示非常紧凑(但仍然可以区分 0m 和 0.0000m 等)。如果我们真的想要,exponent_and_sign 也可以是可选的。
我不知道 Marc 的项目,但在我的端口中,我生成了部分类,因此您可以将 System.Decimal 和 Protobuf.Common.Decimal(或其他)之间的转换放入部分类中。
一种比 Jon 或 Marc 更简单的实现方法是将其存储为 4 个sint32
值,这可以方便地映射到Decimal.GetBits()的输出。
原型文件将如下所示:
message ProtoDecimal {
sint32 v1 = 1;
sint32 v2 = 2;
sint32 v3 = 3;
sint32 v4 = 4;
}
转换器将是:
public decimal ConvertToDecimal(AbideDecimal value)
{
return new decimal(new int[] { value.V1, value.V2, value.V3, value.V4 });
}
public ProtoDecimal ConvertFromDecimal(decimal value)
{
var bits = decimal.GetBits(value);
return new ProtoDecimal
{
V1 = bits[0],
V2 = bits[1],
V3 = bits[2],
V4 = bits[3]
}
}
这在其他语言中可能没有那么简单,但是如果您只需要以 C# 为目标,那么它将占用与其他方法相同的最大 16 个字节(尽管像 0 这样的值可能不会像存储的那样紧凑 - 我不对 protobuf 如何存储 int 的复杂细节有足够的了解),同时对像我这样的愚蠢的程序员来说更清楚:)
显然,如果您想测试性能,您将不得不与马匹比赛,但我怀疑其中有很多。
当您知道小数位数有限时,您可以使用可能的最小单位作为整数值。例如,在处理货币时,不需要小数类型,而是可以定义为使用单位美分。然后一个具有值的整数2
将引用0.02
任何使用的货币。
我用钩子为 protobuf-csharp-port 拼凑了一个补丁,它生成带有原生 Decimal 和 DateTime 结构的 protobuf 类。有线格式明智,它们由两个“内置”原始消息表示。