* (десятичный *) d = XXXm приводит к другому выводу, чем BinaryWriter.Write (XXXm) - PullRequest
0 голосов
/ 02 января 2019

Я пишу оптимизированный бинарный ридер / писатель для обучения самостоятельно.Все работает нормально, пока я не написал тесты для кодирования и декодирования decimal s.Мои тесты также включают, если BinaryWriter .NET Framework создает совместимый вывод для моего BinaryWriter и наоборот.

Я в основном использую небезопасные и указатели для записи моих переменных в байтовые массивы.Таковы результаты, при написании десятичной дроби через указатели и через BinaryWriter:

BinaryWriter....: E9 A8 94 23 9B CA 4E 44 63 C5 44 39 00 00 1A 00
unsafe *decimal=: 00 00 1A 00 63 C5 44 39 E9 A8 94 23 9B CA 4E 44

Мой код написания десятичной дроби выглядит следующим образом:

unsafe
{
    byte[] data = new byte[16];

    fixed (byte* pData = data)
        *(decimal*)pData = 177.237846528973465289734658334m;
}

И с использованием BinaryWriter of.NET Framework выглядит следующим образом:

using (MemoryStream ms = new MemoryStream())
{
    using (BinaryWriter writer = new BinaryWriter(ms))
        writer.Write(177.237846528973465289734658334m);

    ms.ToArray();
}

Microsoft сделала их BinaryWriter несовместимыми с тем, как decimal s хранятся в памяти.Изучив источник ссылок, мы видим, что Microsoft использует внутренний метод, называемый GetBytes, что означает, что выходные данные GetBytes несовместимы с тем, как decimals хранятся в памяти.

Существует ли причина, по которой Microsoft реализовала записьdecimal с таким образом?Может быть опасно использовать способ с unsafe для реализации собственных двоичных форматов или протоколов, потому что в будущем внутренняя структура десятичных чисел может измениться?

Использование способа unsafe работает гораздо лучше, чем использование GetBytes вызывается BinaryWriter.

1 Ответ

0 голосов
/ 02 января 2019

Сама Microsoft пыталась поддерживать decimal и выравнивание своих компонентов как можно более устойчивыми. Вы также можете увидеть это в упомянутом источнике ссылок .NET Framework:

// NOTE: Do not change the order in which these fields are declared. The
// native methods in this class rely on this particular order.
private int flags;
private int hi;
private int lo;
private int mid;

Вместе с использованием [StructLayout(LayoutKind.Sequential)] структура точно выравнивается в памяти точно так же.

Вы получаете неправильные результаты из-за метода GetBytes, использующего переменные, которые строят данные decimal внутри , а не в порядке их выравнивания в самой структуре:

internal static void GetBytes(Decimal d, byte[] buffer)
{
    Contract.Requires((buffer != null && buffer.Length >= 16), "[GetBytes]buffer != null && buffer.Length >= 16");
    buffer[0] = (byte)d.lo;
    buffer[1] = (byte)(d.lo >> 8);
    buffer[2] = (byte)(d.lo >> 16);
    buffer[3] = (byte)(d.lo >> 24);

    buffer[4] = (byte)d.mid;
    buffer[5] = (byte)(d.mid >> 8);
    buffer[6] = (byte)(d.mid >> 16);
    buffer[7] = (byte)(d.mid >> 24);

    buffer[8] = (byte)d.hi;
    buffer[9] = (byte)(d.hi >> 8);
    buffer[10] = (byte)(d.hi >> 16);
    buffer[11] = (byte)(d.hi >> 24);

    buffer[12] = (byte)d.flags;
    buffer[13] = (byte)(d.flags >> 8);
    buffer[14] = (byte)(d.flags >> 16);
    buffer[15] = (byte)(d.flags >> 24);
}

Мне кажется, что соответствующий разработчик .NET попытался адаптировать формат, представленный GetBytes, к little-endian, но допустил одну ошибку. Он упорядочил не только байты компонентов decimal, но и сами компоненты. (flags, hi, lo, mid становится lo, mid, hi, flags.) Но макет с прямым порядком байтов применяется только к полям, а не к целым struct с - особенно с [StructLayout(LayoutKind.Sequential)].

Мой совет здесь обычно заключается в том, чтобы использовать методы, которые Microsoft предлагает в своих классах. Поэтому я бы предпочел любой способ сериализации данных на основе GetBytes или GetBits, чем с помощью unsafe, потому что Microsoft будет поддерживать совместимость с BinaryWriter любым способом. Однако комментарии довольно серьезны, и я бы не ожидал, что Microsoft нарушит платформу .NET на этом базовом уровне.

Мне трудно поверить, что производительность имеет решающее значение для unsafe, а не GetBits. Ведь мы говорим о decimal здесь. Вы все еще можете нажать int из GetBits через unsafe в свой byte[].

...