Понимание значения CRC32 как остатка от деления

Я борюсь с пониманием алгоритма CRC. Я читал этот учебник, и если я понял его правильно, значение CRC является просто остатком от деления, где сообщение служит делителем, а делитель является предопределенным значением - выполняется в особом виде полиномиальной арифметики. Это выглядело просто, поэтому я попытался реализовать CRC-32:

public static uint Crc32Naive(byte[] bytes)
{
    uint poly = 0x04c11db7; // (Poly)
    uint crc = 0xffffffff; // (Init)

    foreach (var it in bytes)
    {
        var b = (uint)it;
        for (var i = 0; i < 8; ++i)
        {
            var prevcrc = crc;

            // load LSB from current byte into LSB of crc (RefIn)
            crc = (crc << 1) | (b & 1); 
            b >>= 1;

            // subtract polynomial if we've just popped out 1 
            if ((prevcrc & 0x80000000) != 0) 
                crc ^= poly;
        }
    }

    return Reverse(crc ^ 0xffffffff); // (XorOut) (RefOut)
}

(где Reverese функция меняет порядок бит)

Предполагается, что он аналогичен следующему методу деления (с некоторыми дополнительными корректировками):

            1100001010
       _______________
10011 ) 11010110110000
        10011,,.,,....
        -----,,.,,....
         10011,.,,....
         10011,.,,....
         -----,.,,....
          00001.,,....
          00000.,,....
          -----.,,....
           00010,,....
           00000,,....
           -----,,....
            00101,....
            00000,....
            -----,....
             01011....
             00000....
             -----....
              10110...
              10011...
              -----...
               01010..
               00000..
               -----..
                10100.
                10011.
                -----.
                 01110
                 00000
                 -----
                  1110 = Remainder

За: 0x00 функция возвращает 0xd202ef8d что правильно, но для 0x01 - 0xd302ef8d вместо 0xa505df1b (Я использовал эту страницу, чтобы проверить свои результаты).

Решение от моей реализации имеет больше смысла для меня: увеличение дивиденда на 1 должно только изменить напоминание на 1, верно? Но оказывается, что результат должен выглядеть совершенно иначе. Видимо, я упускаю что-то очевидное. Что это? Как изменение наименее значимого числа в дивиденде может так сильно повлиять на результат?

1 ответ

Это пример CRC со сдвигом влево, который эмулирует деление, с инициализированным CRC = 0 и без дополнения или изменения CRC. Пример кода эмулирует деление, где к байту добавляется 4 байта с нулями [] ({bytes[],0,0,0,0} - дивиденд, делитель 0x104c11db7, частное не используется, а остаток это CRC).

public static uint Crc32Naive(byte[] bytes)
{
    uint poly = 0x04c11db7; // (Poly is actually 0x104c11db7)
    uint crc = 0;           // (Init)

    foreach (var it in bytes)
    {
        crc ^= (((int)it)<<24);     // xor next byte to upper 8 bits of crc
        for (var i = 0; i < 8; ++i) // cycle the crc 8 times
        {
            var prevcrc = crc;
            crc = (crc << 1); 
            // subtract polynomial if we've just popped out 1 
            if ((prevcrc & 0x80000000) != 0) 
                crc ^= poly;
        }
    }
    return crc;
}

Обычно инициализируют CRC чем-то отличным от нуля, но это не так часто после посткомплементации CRC, и я не знаю ни одного CRC, который делает пост-битное обращение CRC.

Другой вариант CRC - это тот, который сдвигается вправо, обычно используемый для эмуляции аппаратного обеспечения, когда данные отправляются в младшем значащем байте в первую очередь.

Другие вопросы по тегам