Я программирую в 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;
}
Я не совсем уверен, но это кажется результатами, которые я получаю из этого кода, являются неправильными. Так, мой код неправильно и если так, почему?
Вы не используете знак, расширяющий 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);
Поскольку положительная и отрицательная величины одинаковы после сложения, это должно правильно масштабироваться в обоих направлениях.
Похоже, что вы рассматриваете его как 24-битное беззнаковое целое. Если старший бит равен 1, вам нужно сделать i
отрицательным, установив остальные 8 бит также в 1.
Поскольку вы используете массив символов, из этого не следует, что входные данные являются little endian в силу того, что они x86; массив символов делает порядок байтов независимым от архитектуры.
Ваш код несколько переусложнен. Простым решением является сдвиг 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) ;
}
Test code:
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
}