2 の累乗でそれを行う方法を理解しているので、それは私の質問ではありません。
たとえば、整数除算の代わりにビット シフトを使用して数値の 5% を見つけたい場合、どのように計算すればよいでしょうか?
したがって、(x * 20 / 19) の代わりに (x * 100 >> 11) を実行できます。これは正しくありませんが、近いので、試行錯誤を重ねてこれにたどり着きました。使用する最も正確なシフトを決定するにはどうすればよいですか?
2 の累乗でそれを行う方法を理解しているので、それは私の質問ではありません。
たとえば、整数除算の代わりにビット シフトを使用して数値の 5% を見つけたい場合、どのように計算すればよいでしょうか?
したがって、(x * 20 / 19) の代わりに (x * 100 >> 11) を実行できます。これは正しくありませんが、近いので、試行錯誤を重ねてこれにたどり着きました。使用する最も正確なシフトを決定するにはどうすればよいですか?
最善の方法は、コンパイラに任せることです。あなたは単に書く
a/b
選択した言語で、コンパイラはビットいじりを生成します。
編集(気にしないでください。回答に補強を追加しています:
#include <stdio.h>
int main(int argc, char **argv) {
printf("%d\n", argc/4);
}
明らかに、最も速いのは ですargc>>2
。しばらく様子を見てみましょう:
.file "so3.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl 8(%ebp), %eax
movl %eax, %edx
sarl $31, %edx
shrl $30, %edx
leal (%edx,%eax), %eax
sarl $2, %eax
movl %eax, %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
はい、そこです。sarl $2, %eax
EDIT 2(重ねて申し訳ありません20/19
が、もう少し複雑です…)
私はちょうど代入argc*20/19
しargc/4
、これが出てくる数学です:
0000000100000f07 shll $0x02,%edi
0000000100000f0a movl $0x6bca1af3,%edx
0000000100000f0f movl %edi,%eax
0000000100000f11 imull %edx
0000000100000f13 sarl $0x03,%edx
0000000100000f16 sarl $0x1f,%edi
0000000100000f19 subl %edi,%edx
したがって、プロセスは
あなたがやろうとしていることは、結果として得られるプロセスを最適化しないため、意味がありません!!!
ねえ、私はあなたの質問のどこにも、あなたが最適化する意図があることを読んでいませんでした.
電工人は「役に立つ」とはいえ好奇心旺盛。私たちは、屋根裏部屋、地下室、寝室、リビングルームに、いつか役に立つと信じているがらくたを積み上げている、ニュースで読んだアイテムの強迫的な強迫的な買いだめのようなものです。少なくとも、私が 30 年弱前に Engg スクールに通っていたときはそうでした。あなたの人生やライフスタイルを最適化する可能性がほとんどないように見える「役に立たない」知識をため込むための探求を続けることをお勧めします. 手作業でコーディングされたアルゴリズムで実行できるのに、なぜコンパイラに依存するのですか?! ええ?少し冒険してくださいね。あなたの知識の追求に軽蔑を表明する人々を否定することはありません。
中学校で部活のやり方を覚えていますか? 437/24、例えば
_____
24|437
018
-----
24|437
24
-----
197
24
-----
5
割り算の対象となる数 437 は、被除数と呼ばれます。24 は除数、結果の 18 は商、5 は剰余です。税金を申告するときと同様に、株式の「配当」から得た利益を申告する必要がありますが、これは誤解です。納税フォームに記入するのは、配当の 1 つの巨大なチャンクの商の倍数です。配当を受け取ったのではなく、配当の一部を受け取りました。それ以外の場合は、株式の 100% を所有していたことになります。
___________
11000|110110101
000010010
-----------
11000|110110101
11000
----------
000110101 remainder=subtract divisor from dividend
11000000 shift divisor right and append 0 to quotient until
1100000 divisor is not greater than remainder.
110000 Yihaa!
----------
000101 remainder=subtract shifted divisor from remainder
11000 shift divisor right and append 0 to quotient until
1100 divisor is not greater than remainder.
----------
oops, cannot shift anymore.
上記は、すでにご存知かもしれませんが、真の除算です。これは、シフトされた除数を減算することによって達成されます。
あなたが望むのは、被除数をシフトするだけで同じことを達成することです。残念ながら、除数が 2 の指数乗 (2,4,8,16) でない限り、これを行うことはできません。これは、バイナリ算術の明らかな事実です。または、少なくとも、近似および内挿手法なしでそれを実行できる方法を知りません。
したがって、被除数シフトと真の除算を組み合わせて使用する必要があります。例えば
24 = 2 x 2 x 2 x 3
まず、バイナリ シフトを使用して 437 を 8 で除算して 010010 を取得し、次に真の除算を使用して 3 で除算します。
010010
--------
11|110110
11
-------
011
11
-----
0
これは 010010 = 18 になります。
出来上がり。
24 = 2^8 x 3 をどのように決定しますか?
1 になるまで 11000 を右にシフトします。
つまり、除数が 1 になるまで、除数をシフトするのと同じ回数、被除数をシフトできます。
したがって、明らかに、除数が奇数の場合、この方法は機能しません。たとえば、除数 25 では機能しませんが、除数 50 では少し機能します。
おそらく、13 のような除数を 2^3=8 と 2^4=16 の間に補間できる予測方法があります。もしあれば、私はそれらに精通していません。
調べる必要があるのは、数列を使用することです。たとえば、25 で割ると次のようになります。
1 1 1 1 1
__ = __ - ___ - ___ + ___ - ... until the precision you require.
25 16 64 128 256
級数の一般形は
1 1 b1 bn
_ = ___ + _______ + ... + ______
D 2^k 2^(k+1) 2^(k+n)
ここで、bn は -1、0、または +1 のいずれかです。
上記のバイナリ操作にエラーやタイプミスがないことを願っています。もしそうなら、何千もの謝罪。
式があるとしますa = b / c
。hroptatyr が述べたように、乗算は非常に高速です (そして除算よりもはるかに高速です)。したがって、基本的な考え方は、除算を : のような乗算に変換することですa = b * (1/c)
。
ここで、 reciprical の計算にはまだ除算が必要な1/c
ので、これはc
がアプリオリにわかっている場合にのみ機能します。浮動小数点の計算ではそれで十分ですが、整数では別のトリックを使用する必要がありc
ます。の値に関心があるため、最終結果を で割る必要があります。2 の累乗を選択した場合、最終分割は高速になります。some_big_number / c
a2 = b * (some_big_number / c)
some_big_number * b/c
b/c
some_big_number
元:
// we'll compute 1/20 of the input
unsigned divide_by_20(unsigned n){
unsigned reciprocal = (0x10000 + 20 - 1) / 20; //computed at compile time, but you can precompute it manually, just to be sure
return (n * reciprocal) >> 16;
}
編集: この方法の良い点は、修正を選択することで、除算の丸め方法を選択できることです (この場合は20 - 1
、ゼロに丸めるためのものでした)。
その背後にある数学に興味がある場合は、Henry S. Warren によるHacker's Delightを読んでください。
最適化されたコードに興味がある場合は、人間が最も読みやすいコードを記述してください。例えば:
int five_percent(int x) {
return x / 20;
}
を使用してこの関数をコンパイルするg++ -O2
と、実際の除算は行われず、代わりに魔法の乗算、ビットシフト、および修正が行われます。
すべてをシフトで行うことはできません。代わりに「魔法の」除数を使用する必要があります (ハッカーの喜びを参照)。魔法の除算は、ある数値に適切な大きさの別の数値を掛けて、除算の答えが得られるようにロールオーバーすることによって機能します (mul/imul は div/idiv よりも高速です)。魔法の定数は素数ごとにのみ一意であり、倍数にはシフトが必要です。たとえば、3 による符号なし除算は (32 ビットで) として表すことができx * 0xAAAAAAAB
ます(x * 0xAAAAAAAB) >> 1
。等比級数3 * (2 ^ x)
、ここで 0 <= x < 32)
yを掛けてnをシフトすることにより、xの5%を概算するとします。5%は1/20であり、a >> n = a / 2 nなので、解く必要があります
x/20≈x*y/ 2 n (記号「≈」は「ほぼ等しい」を意味します)
これは単純化して
y≈2n / 20
したがって、n = 11の場合、
y≈2n /20 = 2048/20 = 102 + 8/20
したがって、y = 102を設定できます。これは、試行錯誤で見つけた100よりも実際に優れています。
一般に、nで遊んで、より良い答えが得られるかどうかを確認できます。
私はこれを分数1/20で解決しましたが、同じ方法に従うことで、任意の分数p/qでこれを解決できるはずです。
まあ一般的に:
<< 2
l
ます。a * l = a * (l - 1) + a
l - 1
除算も同様に構築できます。