お気に入りのビット単位のテクニックは何ですか?[閉まっている]


14

数日前、StackExchangeのメンバーであるAntoは、ビット単位の演算子の有効な使用方法について問い合わせました私は、整数を2のべき乗で乗算および除算するよりも高速であると述べました。StackExchangeメンバーのDaeminは、右シフトが負の数の問題を引き起こしたと述べて反論しました。

その時点で、符号付き整数でシフト演算子を使用することを考えたことはありませんでした。私は主に低レベルのソフトウェア開発でこの手法を使用しました。したがって、常に符号なし整数を使用しました。Cは、符号なし整数で論理シフトを実行します。論理右シフトを実行する場合、符号ビットには注意が払われません。空白ビットはゼロで埋められます。ただし、Cは符号付き整数を右にシフトするときに算術シフト演算を実行します。空白ビットは符号ビットで埋められます。この違いにより、負の値はゼロに切り捨てられるのではなく、無限に丸められます。これは、符号付き整数除算とは異なる動作です。

数分考えた結果、一次解決策が生まれました。このソリューションは、シフトする前に条件付きで負の値を正の値に変換します。値は、シフト操作が実行された後、条件付きで負の形式に変換されます。

int a = -5;
int n = 1;

int negative = q < 0; 

a = negative ? -a : a; 
a >>= n; 
a = negative ? -a : a; 

このソリューションの問題は、通常、条件付き割り当てステートメントが少なくとも1つのジャンプ命令に変換されることであり、ジャンプ命令は、両方の命令パスをデコードしないプロセッサーでは高価になる可能性があります。命令パイプラインを2回再プラ​​イミングしなければならないことは、除算をオーバーシフトすることで得られるパフォーマンスの向上に良い影響を与えます。

上記で述べたように、私は土曜日に条件付き割り当ての問題に対する答えで目が覚めました。算術シフト演算を実行するときに発生する丸めの問題は、2の補数表現を操作する場合にのみ発生します。補数表現では発生しません。この問題を解決するには、シフト操作を実行する前に2の補数値を1の補数値に変換します。次に、1の補数値を2の補数値に変換する必要があります。驚くべきことに、シフト操作を実行する前に負の値を条件付きで変換することなく、この一連の操作を実行できます。

int a = -5;
int n = 1;

register int sign = (a >> INT_SIZE_MINUS_1) & 1

a = (a - sign) >> n + sign;   

2の補数の負の値は、1を引くことで1の補数の負の値に変換されます。反対に、1の補数の負の値は、1を追加することで2の補数の負の値に変換されます。上記のコードは、符号ビットを使用して2の補数から1の補数へ、またはその逆に変換するために機能します。負の値のみに符号ビットが設定されます。したがって、aが正の場合、変数記号はゼロになります。

上記のことで、上記のような他のビット単位のハックがあなたのトリックの袋に入ったと考えることができますか?お気に入りのビット単位のハックは何ですか?私は常に、パフォーマンス指向の新しいビット単位のハッキングを探しています。


3
この質問&アカウント名-世界は再び理にかなって...
JK

+1興味深い質問を私のフォローアップとして、またそうでなければ
アント

また、いくつかの高速パリティ計算を一度行いました。パリティは伝統的にループとビットが設定されている場合のカウントを伴い、そのすべてに多くのジャンプが必要なので、パリティは少し苦痛です。パリティは、シフトとXORを使用して計算できます。その後、これらの束を次々に実行することで、ループとジャンプを回避できます。
すぐに

2
これらのテクニックに関する本が一冊あることをご存知ですか?-ハッカーディライトamazon.com/Hackers-Delight-Henry-S-Warren/dp/0201914654
ニキエ

ええ、ビット操作専用のWebサイトもあります。URLを忘れましたが、グーグルはすぐにそれを表示します。
すぐに

回答:


23

Gosperのハック(HAKMEM#175)が大好きです。これは、数字を取得し、同じビット数で次の数字を取得する非常に巧妙な方法です。たとえば、次のようにkアイテムの組み合わせを生成する場合に役立ちnます。

int set = (1 << k) - 1;
int limit = (1 << n);
while (set < limit) {
    doStuff(set);

    // Gosper's hack:
    int c = set & -set;
    int r = set + c;
    set = (((r^set) >>> 2) / c) | r;
}

7
+1。しかし、これからは、コメントなしでデバッグセッション中にこれを見つけることについて悪夢を見ます。
ニキエ

@ nikie、muahahahaha!(私はプロジェクトオイラーの問題のようなものにこれを使用する傾向があります-私の日々の仕事は多くの組み合わせを必要としません)。
ピーターテイラー

7

高速逆二乗根方法は、私が今まで見たこと平方根の逆数を計算する最も奇妙なビットレベルの技術を使用しています。

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking [sic]
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck? [sic]
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //    y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

高速sqrtも素晴らしいです。カーマックは最高のコーダーの一人に見えます。
ベンジャミン

ウィキペディアにはさらに古いソースがあります。たとえば、beyond3d.com
content / articles /

0

3による除算-実行時ライブラリ呼び出しに頼ることなく。

3による除算(スタックオーバーフローに関するヒントのおかげ)は、次のように近似できることがわかりました。

X / 3 = [(x / 4)+(x / 12)]

そして、X / 12は(x / 4)/ 3です。ここに突然現れる再帰の要素があります。

また、プレイする数字の範囲を制限すると、必要な反復回数を制限できることがわかります。

したがって、2000未満の符号なし整数の場合、以下は高速で単純な/ 3アルゴリズムです。(数字が大きい場合は、ステップを追加するだけです)。コンパイラはこれを完全に最適化するので、結果として高速で小さくなります。

static unsigned short FastDivide3(const unsigned short arg)
{
  符号なしの短いRunningSum。
  符号なしの短いFractionalTwelth。

  RunningSum = arg >> 2;

  FractionalTwelth = RunningSum >> 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  FractionalTwelth >> = 2;
  RunningSum + = FractionalTwelth;

  //精度を高めるために上記の2行をさらに繰り返します

  return RunningSum;
}

1
もちろん、これは非常に曖昧なマイクロコントローラーにのみ関係します。過去20年間に作成された実際のCPUは、整数除算用のランタイムライブラリを必要としません。
MSalters

1
確かに、しかしハードウェア乗算器のない小さなマイクロは実際には非常に一般的です。そして、もしあなたが埋め込まれた土地で働いていて、販売された100万個の製品のそれぞれに0.10ドルを節約したいなら、あなたはいくつかの汚いトリックをよく知っています。そのお金の節約=上司をとても幸せにする余分な利益。
すぐに

まあ、汚い... .0101010101(約1/3)を掛けているだけです。プロヒント:することもできます乗算することにより.000100010001、および101わずか3 bitshiftsを取り、まだよりよい近似を持っている(.010101010101
MSalters

整数のみで浮動小数点を使用せずにこれを行うにはどうすればよいですか?
すぐに

1
ビット単位、x * 101 = x + x << 2.同様に、x * 0.000100010001はx >> 4 + x >> 8 + x >> 12です
。– MSalters

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.