我正在用 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个更重要的字节时,它似乎不起作用)。

编辑: :事实证明,这种方法似乎遇到了一些我尚不完全理解的问题。最好暂时不要使用它。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top