C/C ++ -24ビット署名整数を変換して浮かぶ
-
04-10-2019 - |
質問
私は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 Less 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に設定することにより、負。
それが良いプログラミングの練習かどうかはわかりませんが、これは機能しているようです(少なくとも32ビットLinuxのG ++では、まだ他に何も試していません)。チャールアレイ、特にそれが実際にはチャーアレイではなく、ストリーム(私の場合、それはファイルストリームです)である場合(それはそれの場合) は チャーアレイ、使用できます memcpy
それ以外の istream::read
).
24ビット変数を、署名された32ビットの重要性の低い3バイトにロードするだけです(signed long
)。次に、シフトします long
左側に1つのバージを変えるように、サインビットが意図されている場所に表示されます。最後に、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つの重要なバイトを読んでいるだけでは機能しないようです)。
編集: :この方法には、まだ完全には理解していないいくつかの問題があるようです。当面は使用しないでください。