4

これが私が達成しようとしている目標です:

  • 32ビットのIEEEフロートを30ビットにパックする必要があります。
  • 仮数のサイズを2ビット小さくしてこれを実行したいと思います。
  • 操作自体は可能な限り高速である必要があります。
  • ある程度の精度が失われることは承知しており、これは許容範囲です。
  • この操作がSNaN、QNaN、無限大などの特殊なケースを台無しにしないのであれば、それは利点です。しかし、私はこれをスピードを超えて犠牲にする準備ができています。

この質問は2つの部分で構成されていると思います。

1)仮数の最下位ビットを単純にクリアできますか?私はこれを試しました、そして今のところそれはうまくいきます、しかし多分私はトラブルを求めています...何かのようなもの:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2)1)が失敗する場合がある場合、これを達成するための最速の方法は何でしょうか?

前もって感謝します

4

5 に答える 5

10

これらの再解釈キャストでは、厳密なエイリアシング規則 (C++ 標準のセクション 3.10) に実際に違反しています。コンパイラの最適化をオンにすると、これはおそらくあなたの顔に吹き飛ばされます。

C++ 標準、セクション 3.10 パラグラフ 15 は次のように述べています。

プログラムが、次の型以外の左辺値を介してオブジェクトの保存された値にアクセスしようとした場合、動作は未定義です。

  • オブジェクトの動的タイプ、
  • オブジェクトの動的型の cv 修飾バージョン、
  • オブジェクトの動的タイプに類似したタイプ、
  • オブジェクトの動的な型に対応する符号付きまたは符号なしの型である型、
  • オブジェクトの動的型の cv 修飾バージョンに対応する符号付きまたは符号なしの型である型、
  • 前述の型の 1 つをメンバーに含む集約型または共用体型 (再帰的に、部分集約型または含まれる共用体のメンバーを含む)、
  • オブジェクトの動的型の (おそらく cv 修飾された) 基本クラス型である型、
  • char または unsigned char 型。

具体的には、3.10/15 では、unsigned int 型の左辺値を介して float オブジェクトにアクセスすることはできません。私は実際にこれに噛まれました。私が書いたプログラムは、最適化をオンにした後、動作しなくなりました。どうやら、GCC は float 型の左辺値が int 型の左辺値をエイリアスすることを期待していませんでした。命令は、3.10/15 を悪用する as-if ルールの下でオプティマイザによってシャッフルされ、動作を停止しました。

以下の仮定の下で

  • float は実際には 32 ビットの IEEE-float に対応します。
  • sizeof(float)==sizeof(int)
  • unsigned 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 を組み込み関数として扱います。NaN、無限大、または非常に大きな数値を扱う関数が必要ない場合は、"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 コーディングでは、連続する浮動小数点値が連続する整数にマップされるため (+/- ゼロは無視されます)、仮数オーバーフローが原因で指数が変更されても、これは機能します。このマッピングは、実際には対数を非常によく近似しています。

于 2010-10-02T15:52:04.337 に答える
8

float の 2 つの LSB をやみくもに削除すると、少数の異常な NaN エンコーディングでは失敗する可能性があります。

NaN は exponent=255, mantissa!=0 としてエンコードされますが、IEEE-754 ではどの仮数値を使用すべきかについて何も述べていません。仮数の値が <= 3 の場合、NaN を無限大に変えることができます。

于 2010-10-02T16:14:06.100 に答える
2

タグ付きfloatの使用法を通常の「unsignedint」と誤って混同しないように、構造体にカプセル化する必要があります。

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

ただし、その移植性は保証できません。

于 2010-10-02T17:14:42.913 に答える
2

どのくらいの精度が必要ですか? 16 ビット フロートで十分な場合 (一部の種類のグラフィックスでは十分)、OpenEXR の一部である ILM の 16 ビット フロート (「半分」) は優れており、あらゆる種類の規則に従います (http://www.openexr.com/ )、構造体にパックした後、十分なスペースが残ります。

一方、それらが取る値のおおよその範囲がわかっている場合は、固定小数点を検討する必要があります。それらは、ほとんどの人が認識しているよりも便利です。

于 2011-11-02T23:38:05.090 に答える
1

ほとんどの回答には有効な情報がありますが、私が探していたものとはまったく異なるため、明確な回答として選択することはできません。ということで、結論だけまとめます。

私の質問のパート 1) に投稿した変換方法は、C++ 標準では明らかに間違っているため、float のビットを抽出する他の方法を使用する必要があります。

そして最も重要なことは...IEEE754浮動小数点数に関する応答やその他のソースを読んで理解している限り、仮数から最下位ビットを削除しても問題ありません。sNaN を除いて、主に精度のみに影響します。sNaN は 255 に設定された指数と仮数 != 0 で表されるため、仮数が <= 3 になる状況が発生する可能性があり、最後の 2 ビットを削除すると sNaN が +/-Infinity に変換されます。ただし、CPU での浮動小数点演算中に sNaN が生成されないため、管理された環境下では安全です。

于 2010-10-11T15:00:01.227 に答える