Estoy de acuerdo con Keraba en que necesita usar algo como la codificación Huffman o quizás el algoritmo Lempel-Ziv-Welch. El problema con el empaque de bit de la manera en que está hablando es que tiene dos opciones:
- Elija una constante n tal que se pueda representar el entero más grande.
- Permitir que n varíe de un valor a otro.
La primera opción es relativamente fácil de implementar, pero realmente va a perder mucho espacio a menos que todos los números enteros sean más bien pequeños.
La segunda opción tiene la gran desventaja de que debe transmitir los cambios en n de alguna manera en el flujo de bits de salida. Por ejemplo, cada valor tendrá que tener una longitud asociada a él. Esto significa que está almacenando dos enteros (aunque sean enteros más pequeños) para cada valor de entrada. Es muy probable que incremente el tamaño del archivo con este método.
La ventaja de Huffman o LZW es que crean libros de códigos de tal manera que la longitud de los códigos puede derivarse del flujo de bits de salida sin almacenar realmente las longitudes. Estas técnicas te permiten acercarte mucho al límite de Shannon.
decidí a dar a su idea original (constante n, eliminar los bits no utilizados y el paquete) una oportunidad para la diversión y aquí es la implementación ingenua que se me ocurrió:
#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]);
}
Esto es muy ineficaz porque es pasos un poco a la vez, pero esa fue la forma más fácil de implementarlo sin tener que lidiar con problemas de endianess. No he probado esto tampoco con una amplia gama de valores, solo los que están en la prueba. Además, no hay comprobación de límites y se supone que los búferes de salida son lo suficientemente largos. Entonces, lo que estoy diciendo es que este código probablemente solo sea bueno para fines educativos para que comiences.
por curiosidad, ¿qué usaste al final? –
Nada realmente, el proyecto para el que estaba destinado murió :). Pero a partir de las respuestas aquí y mis necesidades originales, probablemente terminaría usando algunas máscaras y calculando compensaciones a mano. Tal vez usando algunas plantillas inteligentes también. – pajton
3 años después de que me preguntó, finalmente respondí su pregunta implementando un contenedor de acceso aleatorio donde los elementos se empaquetan con fuerza. Ver mi respuesta: http://stackoverflow.com/a/18038506/216063 –