C#Protobuf net:小数字典:零不';我不能正常往返

C#Protobuf net:小数字典:零不';我不能正常往返,c#,decimal,protobuf-net,C#,Decimal,Protobuf Net,我在protobuf net中发现了一个关于十进制零的序列化/反序列化的奇怪错误,我想知道是否有人找到了一个很好的解决方法,或者这是否真的是一个特性 给定一个如上所述的字典,如果我在linqpad中运行: void Main() { { Dictionary<string, decimal> dict = new Dictionary<string, decimal>(); dict.Add("one", 0.0000000m);

我在protobuf net中发现了一个关于十进制零的序列化/反序列化的奇怪错误,我想知道是否有人找到了一个很好的解决方法,或者这是否真的是一个特性

给定一个如上所述的字典,如果我在linqpad中运行:

void Main()
{
    {
        Dictionary<string, decimal> dict = new Dictionary<string, decimal>();
        dict.Add("one", 0.0000000m);
        DumpStreamed(dict);
    }

    {
        Dictionary<string, decimal> dict = new Dictionary<string, decimal>();
        dict.Add("one", 0m);
        DumpStreamed(dict);
    }
}

public static void DumpStreamed<T>(T val)
{
    using (var stream = new MemoryStream())
    {
        Console.Write("Stream1: ");
        ProtoBuf.Serializer.Serialize(stream, val);
        foreach (var by in stream.ToArray())
        {
            Console.Write(by);
        }

        Console.WriteLine();
        Console.Write("Stream2: ");
        stream.Position = 0;
        var item = ProtoBuf.Serializer.Deserialize<T>(stream);
        using(var stream2 = new MemoryStream())
        {
            ProtoBuf.Serializer.Serialize(stream2, item);
            foreach (var by in stream2.ToArray())
            {
                Console.Write(by);
            }

        }
    }

    Console.WriteLine();
    Console.WriteLine("----");
}
有人知道为什么零只在反序列化过程中得到规范化,而不是在序列化过程中得到规范化吗


或者,在序列化/反序列化的字典中,是否有一致地规范化或不一致地规范化十进制零的解决方法

浮点数据类型实际上是包含多个元素的结构。其中包括基准值和将基准值提升到的指数。十进制的c#文档说明如下:

十进制数的二进制表示法由1位符号、96位整数和用于分割整数并指定其小数部分的比例因子组成。比例因子隐式地是数字10,其指数范围为0到28

例如,您可以将1234000表示为

  • 基值为1234000 x 10^0
  • 基值为123000 x 10^1
  • 基值为12300 x 10^2
等等

所以这个问题不仅仅限于零。所有十进制值都可以用多种方式表示。如果您依赖字节流来检查等价性,那么您会遇到很多问题。你真的不应该这样做,因为你肯定会得到误报,而不仅仅是零

至于序列化时的规范化,我认为这是ProtoBuf特有的问题。当然,您可以编写自己的序列化,以采取步骤规范化数据,尽管这可能很难弄清楚。另一种选择是在存储之前将小数转换为某个自定义类,或者将其存储为字符串表示形式,听起来可能很奇怪

如果您对使用一些小数和检查原始数据感兴趣,请参阅该方法。或者,您可以使用此扩展方法查看内存中的表示,并亲自查看:

public static unsafe string ToBinaryHex(this decimal This)
{
    byte* pb = (byte*)&This;
    var bytes = Enumerable.Range(0, 16).Select(i => (*(pb + i)).ToString("X2"));
    return string.Join("-", bytes);
}

是的;问题在于这句善意但可能有害的话:

    if (low == 0 && high == 0) return decimal.Zero;
它忽略了检查
signScale
。它应该是:

    if (low == 0 && high == 0 && signScale == 0) return decimal.Zero;
我将在下一个版本中对此进行调整


(编辑:我最终完全删除了该检查-代码的其余部分只是一些整数移位等,因此拥有“分支”可能比没有更昂贵)

添加了完整的代码块,对此表示抱歉。重要的不是保留零,而是生成的字节流的差异。产生的字节流是不同的,这导致字典周围的误报是不同的。(为“长尾”交叉引用github问题):;protobuf net已经在使用
GetBits
etc,并且知道scale.Hi@marcGravel。我想你在读我的答案,好像它是写给你的。这封信是写给OP的,他把二进制等式和数字等式混为一谈,结果可能是灾难性的;他们只是希望它能够按可预测的方式往返,这是非常合理的。请注意,重要性并不是保持零,而是更多地体现在生成的字节流之间的差异。产生的字节流是不同的,这导致字典周围的误报是不同的。请考虑在你已经很好的回答这方面的评论。老实说,我更关心的是,它悄悄地改变数据的事实;这实际上不是假阳性——而是真阳性:数据不同(无效);你的回答解释了为什么他们可能认为这是假阳性,而实际上是真阳性,但是。。。我宁愿修改代码,使其成为真正的负数:)
    if (low == 0 && high == 0 && signScale == 0) return decimal.Zero;