C / C ++ - конвертировать 24-битное подписанное целое число для плавания
-
04-10-2019 - |
Вопрос
Я программирую в C ++. Мне нужно преобразовать 24-битное подписанное целое число (хранится в 3-байтовом массиве), чтобы поплавок (нормализуя на [-1.0,1.0]).
Платформа MSVC ++ на X86 (что означает, что вход - это маленький Endian).
Я попробовал это:
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;
}
Я не совсем уверен, но кажется, что результаты, которые я получаю от этого кода, неверны. Итак, мой код не так, и если так, почему?
Решение
Вы не знаки, расширяя 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);
Поскольку положительные и отрицательные величины одинаковы после добавления, это должно правильно масштабировать оба направления.
Другие советы
Поскольку вы используете массив CHAR, он не обязательно следит за тем, что вход - это маленький Endian в силу 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
}
Поскольку это не симметрично, это, наверное, является лучшим компромиссом.
Карты - ((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;
}
Решение, которое работает для меня:
/**
* 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;
}
Работает на меня:
float convert(const char* stream)
{
int fromStream =
(0x00 << 24) +
(stream[2] << 16) +
(stream[1] << 8) +
stream[0];
return (float)fromStream;
}
Похоже, вы относитесь к этому как 24-битное целое число без знака. Если самый значительный бит 1, вам нужно сделать i
негатив, устанавливая оставшиеся 8 бит до 1.
Я не уверен, что это хорошая практика программирования, но это, кажется, работает (по крайней мере, с G ++ на 32-разрядном Linux, еще не пробовал ни на что еще) и, безусловно, более элегантно, чем извлекать байт по байту Массив CHAR, особенно если это не совсем массив CHAR, а скорее поток (в моем случае, это поток файлов), который вы читаете (если это является Массив Char, вы можете использовать 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 важных байтах).
РЕДАКТИРОВАТЬ: Оказывается, этот метод, похоже, имеет некоторые проблемы, которые я пока не полностью понимаю. Лучше не используйте его на данный момент.