6

我正在用 C++ 编程。我需要将 24 位有符号整数(存储在 3 字节数组中)转换为浮点数(归一化为 [-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;
}

我不完全确定,但我从这段代码中得到的结果似乎是不正确的。那么,我的代码错了吗?如果是,为什么?

4

7 回答 7

11

您没有将 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);

因为加法后正负幅度相同,所以这应该正确缩放两个方向。

于 2010-05-26T19:50:21.393 回答
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

}
于 2010-05-26T20:21:13.103 回答
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;
}
于 2011-11-01T06:36:02.957 回答
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;
}
于 2012-04-20T14:37:53.690 回答
1

为我工作:

float convert(const char* stream)
{
    int fromStream = 
        (0x00 << 24) + 
        (stream[2] << 16) + 
        (stream[1] << 8) + 
         stream[0];

    return (float)fromStream;
}
于 2013-12-20T22:33:50.143 回答
0

我不确定这是否是一种好的编程习惯,但这似乎有效(至少在 32 位 Linux 上使用 g++,还没有在其他任何东西上尝试过),并且肯定比逐字节提取更优雅一个 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 个更重要的字节时,它似乎不起作用)。

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

于 2011-05-12T23:26:14.720 回答
0

看起来您将其视为 24 位无符号整数。如果最高有效位为 1,则需要i通过将其余 8 位也设置为 1 来使之为负。

于 2010-05-26T19:55:40.813 回答