32ビットが30ビット(C ++)にフロートする
-
27-09-2019 - |
質問
これが私が達成しようとしている目標です:
- 32ビットIEEEフロートを30ビットに詰める必要があります。
- マンティッサのサイズを2ビット減らすことでこれをやりたいです。
- 操作自体はできるだけ速くする必要があります。
- ある程度の精度が失われることを知っていますが、これは許容されます。
- この操作がSnan、Qnan、Infinitiesなどの特別なケースを台無しにしないなら、それは利点になりますが、私はこれを速度以上犠牲にする準備ができています。
この質問は2つの部分で構成されていると思います。
1)Mantissaの最も重要な部分を単にクリアするだけで簡単にできますか?私はこれを試しましたが、これまでのところうまくいきますが、トラブルを求めているかもしれません...
float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;
2)1)失敗する場合、これを達成するための最速の方法は何ですか?
前もって感謝します
解決 4
それらのほとんどが有効な情報を持っているので、私が探していたものではないので、私は明確な答えとして答えを選択することはできません。だから私は私の結論を要約するだけです。
質問のパート1)に投稿した変換の方法は、C ++標準で明らかに間違っているため、Floatのビットを抽出する他の方法を使用する必要があります。
そして最も重要なことは、IEEE754フロートに関する応答やその他の情報源を読むことから理解している限り、マンティッサから最も重要なビットを落とすことは大丈夫です。 1つの例外を除いて、それは主に精度のみに影響します。スナン。スナンは255とマンティッサ!= 0に設定された指数で表されるため、マンティッサが<= 3になり、最後の2ビットを落とすとスナンを+/-インフィニティに変換する状況があります。しかし、SNANはCPUの浮動小数点操作中に生成されないため、制御された環境では安全です。
他のヒント
実際に、これらの再解釈キャストを使用して、厳格なエイリアシングルール(C ++標準のセクション3.10)に違反します。これは、コンパイラの最適化をオンにすると、おそらく顔に爆発するでしょう。
C ++標準、セクション3.10パラグラフ15は次のように述べています。
プログラムが次のタイプのいずれか以外のlvalueを介してオブジェクトの保存値にアクセスしようとする場合、動作は未定義です
- オブジェクトの動的なタイプ、
- オブジェクトの動的タイプのCV資格バージョン、
- オブジェクトの動的なタイプに似たタイプ、
- オブジェクトの動的なタイプに対応する署名型または符号なしタイプであるタイプ、
- オブジェクトの動的タイプのCV認定バージョンに対応する署名型または署名されていないタイプであるタイプ、
- メンバー間の前述のタイプの1つを含む総合または組合タイプ(再帰的に、サブアグレジェートまたは封じ込められた組合のメンバーを含む)、
- オブジェクトの動的タイプの(おそらくCV認定)ベースクラスタイプであるタイプ、
- charまたは符号なしのcharタイプ。
具体的には、3.10/15では、タイプの符号なしINTのLValueを介してフロートオブジェクトにアクセスすることができません。私は実際にこれに噛まれました。私が書いたプログラムは、最適化をオンにした後、動作を停止しました。どうやら、GCCは、3.10/15による公正な仮定であるタイプINTのlValueをエイリアスするためにタイプフロートのLValueを期待していなかったようです。指示は、3.10/15を悪用するAS-IFルールの下でオプティマイザーによってシャッフルされ、動作が停止しました。
以下 仮定
- フロートは本当に32ビットIEEE-floatに対応しています、
- sizeof(float)== sizeof(int)
- 署名されていないINTには、パディングビットやトラップ表現がありません
あなたはこのようにそれを行うことができるはずです:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
これは「3.10の溶解」に苦しむことはなく、通常は非常に高速です。少なくともGCCは、MemcPyを本質的な関数として扱います。 nans、無限、または非常に大きい数字で作業する機能が必要な場合は、「r >> 2」を(r+1)>> 2 "に置き換えることで精度を向上させることさえできます。
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
これは、IEEE-754コーディングが連続した浮動小数点値を連続した整数にマップするため、マンティッサオーバーフローのために指数を変更しても機能します(+/-ゼロを無視します)。このマッピングは、実際には対数を非常によく近似しています。
フロートの2つのLSBを盲目的に落とすと、少数の異常なNANエンコーディングで失敗する可能性があります。
NANはExponent = 255、Mantissa!= 0としてエンコードされますが、IEEE-754はどのマンティアッサ値を使用するかについて何も言わない。マンティッサの値が<= 3の場合、ナンを無限に変えることができます!
タグ付きフロートの使用を通常の「符号なしINT」と誤って混合しないように、構造体にカプセル化する必要があります。
#include <iostream>
using namespace std;
struct TypedFloat {
private:
union {
unsigned int raw : 32;
struct {
unsigned int num : 30;
unsigned int type : 2;
};
};
public:
TypedFloat(unsigned int type=0) : num(0), type(type) {}
operator float() const {
unsigned int tmp = num << 2;
return reinterpret_cast<float&>(tmp);
}
void operator=(float newnum) {
num = reinterpret_cast<int&>(newnum) >> 2;
}
unsigned int getType() const {
return type;
}
void setType(unsigned int type) {
this->type = type;
}
};
int main() {
const unsigned int TYPE_A = 1;
TypedFloat a(TYPE_A);
a = 3.4;
cout << a + 5.4 << endl;
float b = a;
cout << a << endl;
cout << b << endl;
cout << a.getType() << endl;
return 0;
}
しかし、私はその携帯性を保証することはできません。
どのくらいの精度が必要ですか? 16ビットフロートで十分であり(いくつかのタイプのグラフィックスに十分)、ILMの16ビットフロート( "Half")、OpenExrの一部は素晴らしいルールに従います(http://www.openexr.com/ )、そしてあなたはそれを構造体に詰めた後、十分なスペースが残っています。
一方、彼らが取るつもりである値のおおよその範囲を知っている場合は、固定点を考慮する必要があります。彼らはほとんどの人が気づいているよりも便利です。