数日前、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の補数に、および逆に変換するために使用されるため機能します。負の値のみに符号ビットが設定されます。したがって、変数signは、aは正です。
上記で述べたように、あなたのトリックのバッグにした上記のような他のビットごとのハックを考えることができますか?あなたの好きなビットワイズハックは何ですか?私は常に新しいパフォーマンス指向のビット単位のハックを探しています。
私はゴスパーのハック(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;
}
高速逆二乗 ルートメソッドは、最も奇妙なビットレベルの手法を使用して、私が今まで見た平方根の逆を計算します。
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;
}
3による除算-ランタイムライブラリの呼び出しに頼ることなく。
3による除算(スタックオーバーフローのヒントのおかげ)は、次のように近似できることがわかります。
X/3 = [(x/4)+(x/12)]
そしてX/12は(x/4)/ 3です。ここに突然再帰の要素が現れます。
また、プレイする数値の範囲を制限すると、必要な反復回数を制限できることがわかります。
したがって、2000未満の符号なし整数の場合、以下は高速で単純な/ 3アルゴリズムです。 (数字が大きい場合は、ステップを追加してください)。コンパイラーはこれを完全に最適化するので、最終的には高速で小型になります。
static unsigned short FastDivide3(const unsigned short arg) { unsigned short RunningSum; unsigned short FractionalTwelth; RunningSum = arg >> 2; FractionalTwelth = RunningSum >> 2; RunningSum + = FractionalTwelth; FractionalTwelth >> = 2; RunningSum + = FractionalTwelth; FractionalTwelth >> = 2; RunningSum + = FractionalTwelth; FractionalTwelth >> = 2 ; RunningSum + = FractionalTwelth; //より精度を上げるために、上記の2行をさらに繰り返します return RunningSum; }
Erlangを見ると、ビット操作を行うための完全なDSLがあります。 SO次のように言うことで、ビットでデータ構造を分解できます:
<> = << 1,17,42:16 >>。
詳細はこちら: http://www.erlang.org/doc/reference_manual/expressions.html#id75782