C / C ++ - конвертировать 24-битное целое число со знаком в плавающее - PullRequest
6 голосов
/ 26 мая 2010

Я программирую на C ++. Мне нужно преобразовать 24-разрядное целое число со знаком (хранящееся в 3-байтовом массиве) в число с плавающей точкой (нормализуется до [-1.0,1.0]).

Платформа MSVC ++ на платформе x86 (что означает, что ввод имеет младший порядок).

Я пробовал это:

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

Я не совсем уверен, но, похоже, результаты, полученные из этого кода, неверны. Итак, мой код неверен, и если да, то почему?

Ответы [ 7 ]

11 голосов
/ 26 мая 2010

Вы не знак, расширяющий 24 бита в целое число; старшие биты всегда будут равны нулю. Этот код будет работать независимо от того, какой у вас размер int:

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

Редактировать: Проблема 2 - ваша постоянная масштабирования. Проще говоря, вы хотите умножить на новый максимум и разделить на старый максимум, предполагая, что 0 остается на уровне 0,0 после преобразования.

const float Q = 1.0 / 0x7fffff;

Наконец, почему вы добавляете 0,5 в финальной конверсии? Я мог бы понять, пытались ли вы округлить до целочисленного значения, но вы идете в другом направлении.

Редактировать 2: Источник, на который вы указываете, имеет очень подробное обоснование вашего выбора. Не так, как я бы выбрал, но, тем не менее, вполне оправданно. Мой совет относительно множителя остается в силе, но максимум отличается из-за добавленного коэффициента 0,5:

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

Поскольку положительные и отрицательные величины одинаковы после сложения, это должно правильно масштабировать оба направления.

4 голосов
/ 27 мая 2010

Поскольку вы используете массив символов, не обязательно следует, что входные данные имеют младший порядок в силу того, что они являются x86;массив char делает архитектуру порядка байтов независимой.

Ваш код несколько сложнее.Простое решение состоит в том, чтобы сдвинуть 24-битные данные для масштабирования их до 32-битного значения (чтобы сработала естественная арифметика со знаком машины), а затем использовать простое соотношение результата с максимально возможным значением (которое INT_MAX меньше 256, посколькувакантных младших 8 бит).

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

Тестовый код:

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 голос
/ 21 декабря 2013

у меня работает:

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

    return (float)fromStream;
}
1 голос
/ 20 апреля 2012

Решение, которое работает для меня:

/**
 * 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 голос
/ 01 ноября 2011

Поскольку это не симметрично, это, вероятно, лучший компромисс.

Карты - ((2 ^ 23) -1) до -1,0 и ((2 ^ 23) -1) до 1,0.

(Примечание: это тот же стиль преобразования, который используется в 24-битных файлах WAV)

float convert( const unsigned char* src )
{
    int i = ( ( src[ 2 ] << 24 ) | ( src[ 1 ] << 16 ) | ( src[ 0 ] << 8 ) ) >> 8;
    return ( ( float ) i ) / 8388607.0;
}
0 голосов
/ 13 мая 2011

Я не уверен, что это хорошая практика программирования, но, похоже, это работает (по крайней мере, с g ++ на 32-битном Linux, еще не пробовал его на чем-либо еще) и, безусловно, более элегантно, чем извлечение побайтов -байт из массива символов, особенно если это не массив символов, а поток (в моем случае это поток файлов), из которого вы читаете (если он равен массив символов, вы можете использовать memcpy вместо istream::read).

Просто загрузите 24-разрядную переменную в менее значимые 3 байта 32-разрядного со знаком (signed long). Затем сдвиньте переменную long на один байт влево, чтобы бит знака появился там, где он должен быть. Наконец, просто нормализуйте 32-битную переменную, и все готово.

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

(Как ни странно, это не работает, когда вы просто читаете в 3 более значимых байта сразу).

РЕДАКТИРОВАТЬ : оказывается, у этого метода, похоже, есть некоторые проблемы, которые я до сих пор не до конца понимаю. Лучше не используйте его пока.

0 голосов
/ 26 мая 2010

Похоже, вы рассматриваете его как 24-разрядное целое число без знака. Если старший значащий бит равен 1, вам нужно сделать i отрицательным, установив также оставшиеся 8 битов равными 1.

...