C/C++ - Convert 24-bit signed integer to float

前端 未结 7 2110
野的像风
野的像风 2021-01-12 20:27

I\'m programming in C++. I need to convert a 24-bit signed integer (stored in a 3-byte array) to float (normalizing to [-1.0,1.0]).

The platform is MSVC++ on x86 (wh

相关标签:
7条回答
  • 2021-01-12 20:41

    I'm not sure if it's good programming practice, but this seems to work (at least with g++ on 32-bit Linux, haven't tried it on anything else yet) and is certainly more elegant than extracting byte-by-byte from a char array, especially if it's not really a char array but rather a stream (in my case, it's a file stream) that you read from (if it is a char array, you can use memcpy instead of istream::read).

    Just load the 24-bit variable into the less significant 3 bytes of a signed 32-bit (signed long). Then shift the long variable one byte to the left, so that the sign bit appears where it's meant to. Finally, just normalize the 32-bit variable, and you're all set.

    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);
    }
    

    (Strangely, it doesn't seem to work when you just read into the 3 more significant bytes right away).

    EDIT: it turns out this method seems to have some problems I don't fully understand yet. Better don't use it for the time being.

    0 讨论(0)
  • 2021-01-12 20:45

    The solution that works for me:

    /**
     * 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;
    }
    
    0 讨论(0)
  • 2021-01-12 21:00

    Since it's not symmetrical, this is probably the best compromise.

    Maps -((2^23)-1) to -1.0 and ((2^23)-1) to 1.0.

    (Note: this is the same conversion style used by 24 bit WAV files)

    float convert( const unsigned char* src )
    {
        int i = ( ( src[ 2 ] << 24 ) | ( src[ 1 ] << 16 ) | ( src[ 0 ] << 8 ) ) >> 8;
        return ( ( float ) i ) / 8388607.0;
    }
    
    0 讨论(0)
  • 2021-01-12 21:02

    You are not sign extending the 24 bits into an integer; the upper bits will always be zero. This code will work no matter what your int size is:

    if (i & 0x800000)
        i |= ~0xffffff;
    

    Edit: Problem 2 is your scaling constant. In simple terms, you want to multiply by the new maximum and divide by the old maximum, assuming that 0 remains at 0.0 after conversion.

    const float Q = 1.0 / 0x7fffff;
    

    Finally, why are you adding 0.5 in the final conversion? I could understand if you were trying to round to an integer value, but you're going the other direction.

    Edit 2: The source you point to has a very detailed rationale for your choices. Not the way I would have chosen, but perfectly defensible nonetheless. My advice for the multiplier still holds, but the maximum is different because of the 0.5 added factor:

    const float Q = 1.0 / (0x7fffff + 0.5);
    

    Because the positive and negative magnitudes are the same after the addition, this should scale both directions correctly.

    0 讨论(0)
  • 2021-01-12 21:03

    Looks like you're treating it as an 24-bit unsigned integer. If the most significant bit is 1, you need to make i negative by setting the remaining 8 bits to 1 as well.

    0 讨论(0)
  • 2021-01-12 21:04

    Since you are using a char array, it does not necessarily follow that the input is little endian by virtue of being x86; the char array makes the byte order architecture independent.

    Your code is somewhat over complicated. A simple solution is to shift the 24 bit data to scale it to a 32bit value (so that the machine's natural signed arithmetic will work), and then use a simple ratio of the result with the maximum possible value (which is INT_MAX less 256 because of the vacant lower 8 bits).

    #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) ;
    }
    

    Test code:

    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
    
    }
    
    0 讨论(0)
提交回复
热议问题