2010-05-26 18 views
6

Estoy programando en C++. Necesito convertir un entero con signo de 24 bits (almacenado en una matriz de 3 bytes) para flotar (normalizando a [-1.0,1.0]).C/C++ - Convertir entero de 24 bits con signo para flotar

La plataforma es MSVC++ en x86 (lo que significa que la entrada es little-endian).

yo probamos este:

float convert(const unsigned char* src) 
{ 
    int i = src[2]; 
    i = (i << 8) | src[1]; 
    i = (i << 8) | src[0]; 

    const float Q = 2.0/((1 << 24) - 1.0); 

    return (i + 0.5) * Q; 
} 

No estoy del todo seguro, pero parece que los resultados que estoy obteniendo de este código son incorrectos. Entonces, ¿mi código está equivocado? Si es así, ¿por qué?

+0

El código real está en C++. No es mi código, y no dije que fuera bonito. –

Respuesta

9

No se muestra extendiendo los 24 bits en un entero; los bits superiores siempre serán cero. Este código funcionará sin importar cuál sea su tamaño es int:

if (i & 0x800000) 
    i |= ~0xffffff; 

Editar: Problema 2 es su constante de escala. En términos simples, quiere multiplicar por el nuevo máximo y dividir por el máximo anterior, suponiendo que 0 permanece en 0.0 después de la conversión.

const float Q = 1.0/0x7fffff; 

Por último, ¿por qué la adición de 0,5 en la conversión final? Podría entender si estabas tratando de redondear a un valor entero, pero vas en la otra dirección.

Editar 2: La fuente que señala tiene un fundamento muy detallado para sus elecciones. No de la manera que habría elegido, pero perfectamente defendible de todos modos. Mi consejo para el multiplicador aún mantiene, pero el máximo es diferente debido al factor de 0,5 añadido:

const float Q = 1.0/(0x7fffff + 0.5); 

Debido a que las magnitudes positivas y negativas son los mismos después de la adición, esto debe escalar ambas direcciones correctamente.

+1

El rango de 24 ints no es simétrico alrededor de 0. Hay más entradas negativas (por una) que las positivas. El OP quiere mapear en un intervalo cerrado [-1.0, 1.0], y supongo que para eso es el agregado de 0.5. –

+0

@Maciej Hehl, de hecho, el rango de entrada no es simétrico, pero sospecho que el rango práctico es. Si es realmente necesario mapear el rango completo a [-1.0,1.0], entonces tiene dos opciones: multiplicadores diferentes para números positivos y negativos, o 0! = 0.0. Yo no elegiría ninguno. –

+1

Maciej tiene razón, vea los comentarios al principio de este archivo: http://bit.ly/bog0nN (ese es el archivo en el que estoy trabajando, por cierto, el soporte de 24 bits se rompe de muchas maneras, y tiene que arreglarlo). –

0

Parece que lo está tratando como un entero sin signo de 24 bits. Si el bit más significativo es 1, debe hacer que i sea negativo al establecer los 8 bits restantes en 1 también.

3

Dado que está utilizando una matriz de caracteres, no necesariamente se deduce que la entrada es poco endian en virtud de ser x86; la matriz char hace que la arquitectura de orden de bytes sea independiente.

Su código es algo complicado. Una solución simple es cambiar los datos de 24 bits para escalarlos a un valor de 32 bits (para que funcione la aritmética natural firmada de la máquina), y luego usar una relación simple del resultado con el valor máximo posible (que es INT_MAX menos 256 porque de los 8 bits inferiores vacantes).

#include <limits.h> 

float convert(const unsigned char* src) 
{ 
    int i = src[2] << 24 | src[1] << 16 | src[0] << 8 ; 
    return i/(float)(INT_MAX - 256) ; 
} 

Código de ensayo:

unsigned char* makeS24(unsigned int i, unsigned char* s24) 
{ 
    s24[2] = (unsigned char)(i >> 16) ; 
    s24[1] = (unsigned char)((i >> 8) & 0xff); 
    s24[0] = (unsigned char)(i & 0xff); 
    return s24 ; 
} 

#include <iostream> 

int main() 
{ 
    unsigned char s24[3] ; 
    volatile int x = INT_MIN/2 ; 

    std::cout << convert(makeS24(0x800000, s24)) << std::endl ; // -1.0 
    std::cout << convert(makeS24(0x7fffff, s24)) << std::endl ; // 1.0 
    std::cout << convert(makeS24(0, s24)) << std::endl ;   // 0.0 
    std::cout << convert(makeS24(0xc00000, s24)) << std::endl ; // -0.5 
    std::cout << convert(makeS24(0x400000, s24)) << std::endl ; // 0.5 

} 
+1

El orden de bytes se definió como" little endian "en la descripción del problema original. –

+0

@Mark Ransom: Lo extrañé, pero la conclusión" lo que significa que la entrada es little-endian "no sigue de" La plataforma es MSVC++ en x86 "; como se usa una matriz char en lugar de una int, el orden de bytes podría ser independiente de la arquitectura. Sin embargo, tienes razón, tengo eliminó la declaración – Clifford

0

No estoy seguro de si se trata de una buena práctica de programación, pero esto parece funcionar (al menos con g ++ en Linux de 32 bits, no se han probado en cualquier otra cosa) y es ciertamente más elegante que extraer byte a byte de una matriz de caracteres, especialmente si no es realmente una matriz de caracteres, sino más bien una secuencia (en mi caso, es una secuencia de archivos) de la que lees (si es una matriz de caracteres, puede usar memcpy en lugar de istream::read).

Simplemente cargue la variable de 24 bits en los 3 bytes menos significativos de un firmado 32-bit (signed long). A continuación, cambie la variable long un byte a la izquierda, de modo que el bit de signo aparezca donde debe. Finalmente, simplemente normalice la variable de 32 bits, y ya está todo listo.

union _24bit_LE{ 
    char access; 
    signed long _long; 
}_24bit_LE_buf; 

float getnormalized24bitsample(){ 
    std::ifstream::read(&_24bit_LE_buf.access+1, 3); 
    return (_24bit_LE_buf._long<<8)/(0x7fffffff + .5); 
} 

(Curiosamente, parece que no funciona cuando acaba de leer los 3 bytes más importantes de inmediato).

EDIT: resulta que este método parece tener algunos problemas que todavía no entiendo. Mejor no lo use por el momento.

1

Dado que no es simétrico, este es probablemente el mejor compromiso.

Mapas - ((2^23) -1) a -1.0 y ((2^23) -1) a 1.0.

(Nota: este es el mismo estilo de conversión utilizado por los archivos WAV de 24 bits)

float convert(const unsigned char* src) 
{ 
      int i = ((src[ 2 ] << 24) | (src[ 1 ] << 16 ) | (src[ 0 ] << 8)) >> 8; 
    return ((float) i) / 8388607.0; 
} 
1

La solución que funciona para mí:

/** 
* Convert 24 byte that are saved into a char* and represent a float 
* in little endian format to a C float number. 
*/ 
float convert(const unsigned char* src) 
{ 
    float num_float; 
    // concatenate the chars (short integers) and 
    // save them to a long int 
    long int num_integer = (
      ((src[2] & 0xFF) << 16) | 
      ((src[1] & 0xFF) << 8) | 
      (src[0] & 0xFF) 
     ) & 0xFFFFFFFF; 

    // copy the bits from the long int variable 
    // to the float. 
    memcpy(&num_float, &num_integer, 4); 

    return num_float; 
} 
1

funciona para mí:

float convert(const char* stream) 
{ 
    int fromStream = 
     (0x00 << 24) + 
     (stream[2] << 16) + 
     (stream[1] << 8) + 
     stream[0]; 

    return (float)fromStream; 
} 
Cuestiones relacionadas