Pregunta

estoy programando en C ++. Necesito convertir un entero con signo de 24 bits (almacenados en una matriz de 3 bytes) para flotar (normalización a [-1.0,1.0]).

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

He intentado esto:

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. Por lo tanto, es mi código incorrecto y si es así, ¿por qué?

¿Fue útil?

Solución

No son extender el signo de los 24 bits en un número entero; los bits superiores siempre será cero. Este código funcionará sin importar cuál sea su tamaño int es:

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

Editar Problema 2 es su constante de escala. En términos simples, usted quiere multiplicar por el nuevo máximo y dividirlo por la vieja máxima, asumiendo que se mantiene en 0 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 estuviera tratando de rodear a un valor entero, pero vas la otra dirección.

Editar 2: La fuente señala a tiene una razón de ser muy detallado para sus opciones. No es la manera en que yo hubiera elegido, pero perfectamente defendible, no obstante. Mi consejo para el multiplicador aún mantiene, pero el máximo es diferente debido al factor añadido 0,5:

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

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

Otros consejos

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

Su código es algo más complicado. Una solución sencilla es cambiar los datos de 24 bits a escala a un valor de 32 bits (de modo que de trabajo natural firmado aritmética voluntad de la máquina), y luego usar una relación sencilla del resultado con el valor máximo posible (que es INT_MAX menos 256 porque de las vacantes 8 bits inferiores).

#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

}

Ya que no es simétrica, 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;
}

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;
}

funciona para mí:

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

    return (float)fromStream;
}

Parece que eres tratándolo como un entero sin signo de 24 bits. Si el bit más significativo es 1, es necesario hacer i negativa mediante el establecimiento de los 8 bits restantes a 1 también.

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 aún) y es sin duda más elegante que la extracción de byte -byte a partir de una matriz de caracteres, especialmente si no es realmente una matriz de caracteres, sino más bien una corriente (en mi caso, se trata de una secuencia de archivo) que lea a partir de (si es una matriz de caracteres, puede uso memcpy en lugar de istream::read).

Sólo carga la variable de 24 bits en los menos significativos 3 bytes de un 32 bits con signo (signed long). Luego cambie la variable de un byte long a la izquierda, de modo que el bit de signo aparece donde se supone que debe. Por último, simplemente normalizar la variable de 32 bits, y eso es todo.

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, no parece funcionar cuando se acaba de leer en los 3 bytes más significativos de inmediato).

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top