C # Protobuf-net: Словарь десятичных дробей: Нули не получают правильную передачу туда и обратно - PullRequest
0 голосов
/ 12 июня 2018

Я обнаружил странную ошибку, связанную с сериализацией / десериализацией десятичных нулей в 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("----");
}

Я получу два разных потока:

Первая сериализация: 1091031111101011822414

Вторая сериализация: 107103111110101180

(0,0000000m преобразуется в 0 при десериализации).

Я обнаружил, что это связано с этой строкой кода в ReadDecimal:

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

Кто-нибудь знает, почему нулинормализуется только во время десериализации, а не при сериализации?

Или какой-нибудь обходной путь для последовательной нормализации или последовательного ненормирования десятичного нуля в словаре по сериализации / десериализации?

Ответы [ 2 ]

0 голосов
/ 13 июня 2018

Да;проблема в этой благонамеренной, но потенциально вредной строке:

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

, которая пренебрегает проверкой signScale.На самом деле это должно быть:

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

Я подправлю это для следующей сборки.

(редактировать: я полностью удалил эту проверку - остальная часть кода - просто целое числосдвиги и т. д., поэтому «ветка», вероятно, дороже, чем , а не , имеющая ее)

0 голосов
/ 12 июня 2018

Типы данных с плавающей точкой на самом деле представляют собой структуры с несколькими элементами.Среди них базовая стоимость и показатель степени, до которой необходимо повысить базовую стоимость.Документация c # для decimal гласит следующее:

Двоичное представление десятичного числа состоит из 1-битного знака, 96-битного целого числа и коэффициента масштабирования, используемого для деленияцелое число и укажите, какая его часть является десятичной дробью.Коэффициент масштабирования - это неявное число 10, возведенное в степень в диапазоне от 0 до 28

Так, например, вы можете представить 1234000 как

  • Базовое значение 1234000 x10 ^ 0
  • Базовое значение 123000 x 10 ^ 1
  • Базовое значение 12300 x 10 ^ 2

и т. Д.

Так что эта проблема не ограничивается только нулем.Все десятичные значения могут быть представлены более чем одним способом.Если вы полагаетесь на байтовые потоки для проверки эквивалентности, у вас много проблем.Вы действительно не должны этого делать, поскольку вы определенно получите ложные срабатывания, а не только для нуля.

Что касается нормализации при сериализации, я думаю, что это проблема, специфичная для ProtoBuf.Вы, конечно, могли бы написать свою собственную сериализацию, которая принимает меры для нормализации данных, хотя это может быть сложно выяснить.Другой вариант - преобразовать десятичные дроби в некоторый пользовательский класс перед хранением или сохранить их в виде их строковых представлений, как бы странно это ни звучало.

Если вы заинтересованы в изучении некоторых десятичных знаков и проверке необработанных данныхсм. метод 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);
}
...