C# Protobuf-net:小数字典:零不能正确往返

C# Protobuf-net: Dictionary of decimals: Zeroes don't get roundtrip properly

我在 protobuf-net 中发现了一个关于 serialization/deserialization 十进制零的奇怪错误,想知道是否有人为此找到了一个好的解决方法,或者这是否真的是一个功能。

给定一个像上面这样的字典,如果我在 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("----");
}

我将获得两个不同的流:

第一次连载:1091031111101011822414

二次连载:1071031111110101180

(0.0000000m 在反序列化时被转换为 0)。

我发现这是由于 ReadDecimal 中的这行代码造成的:

 if (low == 0 && high == 0) return decimal.Zero;

有谁知道为什么零仅在反序列化期间而不是在序列化期间被标准化?

或者在 serialization/deserialization 的字典中始终规范化或始终不规范化小数零的任何解决方法?

浮点数据类型实际上是具有多个元素的结构。其中包括基值和基值要提高到的指数。 decimal 的 c# 文档说明如下:

The binary representation of a Decimal number consists of a 1-bit sign, a 96-bit integer number, and a scaling factor used to divide the integer number and specify what portion of it is a decimal fraction. The scaling factor is implicitly the number 10, raised to an exponent ranging from 0 to 28

例如,您可以将 1234000 表示为

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

等等

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

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

如果您有兴趣处理一些小数并检查原始数据,请参阅 GetBits() 方法。或者您可以使用此扩展方法来查看内存中的表示并亲自查看:

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;

我会在下一个版本中对其进行调整。

(编辑:我最终完全删除了该检查 - 其余代码只是一些整数移位等,因此 "branch" 可能比 not 有了它)