【问题标题】:C/C++ - Convert 24-bit signed integer to floatC/C++ - 将 24 位有符号整数转换为浮点数
【发布时间】:2010-05-26 19:39:47
【问题描述】:

我正在用 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;
}

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

【问题讨论】:

  • 实际代码是 C++。这不是我的代码,我也没说它漂亮。

标签: c++ c floating-point integer 24-bit


【解决方案1】:

您没有将 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 个整数的范围不是围绕 0 对称的。负整数(加一)比正整数多。 OP 想要映射到闭区间 [-1.0, 1.0],我猜这就是添加的 0.5 的用途。
  • @Maciej Hehl,确实输入范围不是对称的,但我怀疑实际范围是。如果确实需要将整个范围映射到 [-1.0,1.0],那么您有两个选择:正数和负数的不同乘数,或者 0 != 0.0。我不会选择任何一个。
  • Maciej 是对的,请参阅此文件开头的 cmets:bit.ly/bog0nN(顺便说一下,这是我正在处理的文件 - 24 位支持在很多方面都被破坏了,并且我必须修复它)。
  • 您的解决方案正在运行。谢谢!关于 Q,您的代码产生的值与我的相同 (-1.0737418e+008)。
  • @e-t172,请参阅我的编辑。我认为这是最后缺失的部分。
【解决方案2】:

由于您使用的是 char 数组,因此输入是 x86 并不一定是 little endian; 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

}

【讨论】:

  • 原问题描述中字节序定义为“小端序”。
  • @Mark Ransom:我确实错过了,但是“这意味着输入是 little-endian”的结论并没有从“平台是 x86 上的 MSVC++”得出;因为使用的是 char 数组而不是 int,所以字节顺序可以独立于体系结构。尽管如此,你是对的,我已经删除了声明。
【解决方案3】:

由于它不是对称的,这可能是最好的折衷方案。

将 -((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;
}

【讨论】:

    【解决方案4】:

    适合我的解决方案:

    /**
     * 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;
    }
    

    【讨论】:

      【解决方案5】:

      为我工作:

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          我不确定这是否是一种好的编程习惯,但这似乎有效(至少在 32 位 Linux 上使用 g++,还没有在其他任何东西上尝试过),而且肯定比逐字节提取更优雅-byte 来自 char 数组,特别是如果它不是真正的 char 数组,而是您从中读取的流(在我的情况下,它是文件流)(如果它 is 是 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 个更重要的字节时,它似乎不起作用)。

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

          【讨论】:

            【解决方案8】:

            这个来自here,适合我。

            typedef union {
                struct {
                    unsigned short lo;
                    unsigned short hi;
                } u16;
                unsigned int u32;
                signed int i32;
                float  f;
            }Versatype32;
            //Bipolar version (-1.0 to ~1.0)
            void fInt24_to_float(float* dest, const char* src, size_t length) {
                Versatype32 xTemp;
                while (length--) {
                    xTemp.u32 = *(int*)src;
                    //Check if Negative by right shifting 8
                    xTemp.u32 <<= 8; //(If it's a negative, we'll know)  (Props to Norman Wong)
                    //Convert to float
                    xTemp.f = (float)xTemp.i32;     
                    //Skip divide down if zero
                    if (xTemp.u32 != 0) {
                        //Divide by (1<<31 or 2^31)
                        xTemp.u16.hi -= 0x0F80; //BAM! Bitmagic!
                    }
                    *dest = xTemp.f;
                    //Move to next set
                    dest++;
                    src += 3;
                }   //Are we done yet?
                //Yes!
                return;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-10-19
              • 1970-01-01
              • 2011-04-23
              • 2014-12-29
              • 2012-01-09
              • 1970-01-01
              相关资源
              最近更新 更多