C/C++ - 将 24 位有符号整数转换为浮点数
-
04-10-2019 - |
题
我正在用 C++ 编程。我需要将 24 位有符号整数(存储在 3 字节数组中)转换为浮点数(标准化为 [-1.0,1.0])。
该平台是 x86 上的 MSVC++(这意味着输入是小端字节序)。
我试过这个:
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 数组,因此输入不一定是 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来为1。
我不确定这是否是一个很好的编程练习,但这似乎可以起作用(至少在32位Linux上使用G ++,还没有尝试过任何其他尝试),而且比从字节中提取字节更优雅一个char数组,尤其是如果它不是真正的字符阵列,而是您从中读取的流(就我而言,是一个文件流) 是 一个char阵列,您可以使用 memcpy
代替 istream::read
).
只需将24位变量加载到签名的32位的不太显着的3个字节中(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个更重要的字节时,它似乎不起作用)。
编辑: :事实证明,这种方法似乎遇到了一些我尚不完全理解的问题。最好暂时不要使用它。