Я согласен с керабой, что вам нужно использовать что-то вроде кодирования Хаффмана или, возможно, алгоритма Лемпеля-Зива-Уэлша. Проблема с битовой упаковкой, о которой вы говорите, состоит в том, что у вас есть два варианта:
- Выберите константу n, чтобы было представлено наибольшее целое число.
- Разрешить n варьироваться от значения к значению.
Первый вариант относительно прост в реализации, но на самом деле собирается потратить много места, если все целые числа не являются достаточно маленькими.
Второй вариант имеет главный недостаток, заключающийся в том, что вы должны каким-либо образом передавать изменения n в выходном битовом потоке. Например, каждое значение должно иметь длину, связанную с ним. Это означает, что вы сохраняете два целых числа (хотя и меньшие целые числа) для каждого входного значения. У вас есть хороший шанс увеличить размер файла с помощью этого метода.
Преимущество Huffman или LZW заключается в том, что они создают кодовые книги таким образом, что длина кодов может быть получена из выходного битового потока без фактического хранения длин. Эти методы позволяют вам приблизиться к пределу Шеннона.
я решил дать вашу оригинальную идею (константа п, удалить неиспользуемые биты и пакет) попробовать для развлечения и вот наивная реализация я придумал:
#include <sys/types.h>
#include <stdio.h>
int pack(int64_t* input, int nin, void* output, int n)
{
int64_t inmask = 0;
unsigned char* pout = (unsigned char*)output;
int obit = 0;
int nout = 0;
*pout = 0;
for(int i=0; i<nin; i++)
{
inmask = (int64_t)1 << (n-1);
for(int k=0; k<n; k++)
{
if(obit>7)
{
obit = 0;
pout++;
*pout = 0;
}
*pout |= (((input[i] & inmask) >> (n-k-1)) << (7-obit));
inmask >>= 1;
obit++;
nout++;
}
}
return nout;
}
int unpack(void* input, int nbitsin, int64_t* output, int n)
{
unsigned char* pin = (unsigned char*)input;
int64_t* pout = output;
int nbits = nbitsin;
unsigned char inmask = 0x80;
int inbit = 0;
int nout = 0;
while(nbits > 0)
{
*pout = 0;
for(int i=0; i<n; i++)
{
if(inbit > 7)
{
pin++;
inbit = 0;
}
*pout |= ((int64_t)((*pin & (inmask >> inbit)) >> (7-inbit))) << (n-i-1);
inbit++;
}
pout++;
nbits -= n;
nout++;
}
return nout;
}
int main()
{
int64_t input[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
int64_t output[21];
unsigned char compressed[21*8];
int n = 5;
int nbits = pack(input, 21, compressed, n);
int nout = unpack(compressed, nbits, output, n);
for(int i=0; i<=20; i++)
printf("input: %lld output: %lld\n", input[i], output[i]);
}
Это очень неэффективно, потому что это шаги по одному бит за раз, но это был самый простой способ реализовать его, не затрагивая проблемы endianess. Я не тестировал это либо с широким диапазоном значений, а только с тестами. Кроме того, проверка границ не выполняется, и предполагается, что выходные буферы достаточно длинные. Так что я говорю, что этот код, вероятно, хорош только для образовательных целей, чтобы вы начали.
Из любопытства, что вы использовали в конце? –
Ничего, проект, предназначенный для умершего :). Но из ответов здесь и моих первоначальных потребностей я, вероятно, в конечном итоге использовал бы некоторые маски и вычисления смещения вручную. Возможно, используя некоторые умные шаблоны. – pajton
Через 3 года после того, как вы спросили, я, наконец, ответил на ваш вопрос, выполнив контейнер произвольного доступа, где элементы упакованы плотно. См. Мой ответ: http://stackoverflow.com/a/18038506/216063 –