2010-05-26 2 views
6

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

Платформа представляет собой 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; 
} 

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

+0

Фактический код находится на C++. Это не мой код, и я не сказал, что это красиво. –

ответ

9

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

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

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

const float Q = 1.0/0x7fffff; 

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

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

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

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

+1

Диапазон 24 ints не симметричен относительно 0. Есть больше отрицательных int (на единицу), чем положительные int. OP хочет отобразить на замкнутый интервал [-1.0, 1.0], и я предполагаю, что это то, для чего добавлен 0.5. –

+0

@Maciej Hehl, действительно, диапазон ввода не симметричен, но я подозреваю, что это практический диапазон. Если действительно необходимо отобразить полный диапазон до [-1.0,1.0], тогда у вас есть два варианта: разные множители для положительных и отрицательных чисел, или 0! = 0.0. Я бы не выбрал ни одного. –

+1

Maciej прав, см. Комментарии в начале этого файла: http://bit.ly/bog0nN (это файл, над которым я работаю, кстати - 24-битная поддержка разбита множеством способов, и я должны исправить это). –

0

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

3

Поскольку вы используете массив символов, это не обязательно следует за тем, что вход немного сужен по причине 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

Порядок байтов был определен как« малоконечный »в описании оригинальной проблемы. –

+0

@Mark Ransom: Я пропустил это, но вывод" что означает, что вход немногочисленный »не следует из« Платформа MSVC++ на x86 », потому что используется массив символов, а не int, порядок байтов может быть независимым от архитектуры. Тем не менее, вы правы, я имею удалил статут. – Clifford

0

Я не уверен, если это хорошая практика программирования, но это, кажется, работает (по крайней мере, с г ++ на 32-битных Linux, не пробовал его на еще что-нибудь еще) и, безусловно, более изящна, чем извлечение байта из байта из массива символов, особенно если это не массив символов, а поток (в моем случае это поток файлов), который вы читаете (если это is a char array, вы можете использовать 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 более значительных байта сразу).

EDIT: выясняется, что этот метод, похоже, имеет некоторые проблемы, которые я еще не совсем понял. Лучше не использовать его пока.

1

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

Карты - ((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; 
} 
1

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

/** 
* 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

работает для меня:

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

    return (float)fromStream; 
} 
Смежные вопросы