我正在用C ++编程。我需要将24位有符号整数(存储在3字节数组中)转换为float(规范化为[-1.0,1.0])。
该平台是x86上的MSVC ++(这意味着输入是little-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;
}
我不完全确定,但似乎我从这段代码得到的结果是不正确的。那么,我的代码是错误的,如果是,为什么?
答案 0 :(得分:10)
您不是将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);
因为加法后的正负幅度相同,所以应该正确地缩放两个方向。
答案 1 :(得分:4)
由于你使用的是一个char数组,因此输入的结果不一定是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 :(得分: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;
}
答案 3 :(得分: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;
}
答案 4 :(得分:1)
适合我:
float convert(const char* stream)
{
int fromStream =
(0x00 << 24) +
(stream[2] << 16) +
(stream[1] << 8) +
stream[0];
return (float)fromStream;
}
答案 5 :(得分:0)
看起来你将它视为一个24位无符号整数。如果最高有效位为1,则需要通过将剩余的8位设置为1来使i
为负。
答案 6 :(得分:0)
我不确定它是不是很好的编程习惯,但这似乎有用(至少在32位Linux上使用g ++,还没有尝试过其他任何东西)并且肯定比提取byte-by更优雅来自char数组的字节,特别是如果它不是一个char数组而是一个你读取的流(在我的例子中,它是一个文件流)(如果它是一个char数组,你可以使用memcpy
代替istream::read
)。
只需将24位变量加载到带符号的32位(signed long
)的不太重要的3个字节中。然后将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个更重要的字节时它似乎不起作用。)
编辑:事实证明这种方法似乎有些问题我还没有完全理解。最好不要暂时使用它。