6

私はC++でプログラミングしています。24ビットの符号付き整数(3バイト配列に格納されている)をfloat([-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;
}

完全にはわかりませんが、このコードから得られる結果は正しくないようです。それで、私のコードは間違っていますか?もしそうなら、なぜですか?

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

24ビットの符号なし整数として扱っているようです。最上位ビットが1の場合i、残りの8ビットも1に設定して、負の値にする必要があります。

于 2010-05-26T19:55:40.813 に答える
0

プログラミングが適切かどうかはわかりませんが、これは機能しているようで(少なくとも、32ビットLinuxのg ++​​では、まだ試していません)、バイトごとに抽出するよりも確かにエレガントです。 char配列、特にそれが実際にはchar配列ではなく、読み取り元のストリーム(私の場合はファイルストリーム)である場合( char配列の場合memcpy、の代わりに使用できますistream::read)。

24ビット変数を符号付き32ビット(signed long)の重要度の低い3バイトにロードするだけです。次に、変数を1バイト左にシフトして、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 に答える