モジュラスはすでに説明されていますが、要約してみましょう。
k
モジュロの余りを見つけるには、次の2^n-1
ように記述します。
k = a + 2^n*b, 0 <= a < 2^n
それで
k = a + ((2^n-1) + 1) * b
= (a + b) + (2^n-1)*b
≡ (a + b) (mod 2^n-1)
の場合a + b >= 2^n
、余りが。未満になるまで繰り返し、それ2^n
がにつながる場合はa + b = 2^n-1
、それを0に置き換えます。「右にシフトn
して最後のn
ビットに追加」するたびに、最初のセットのビットが右に移動するn
か、n-1
配置されます(k < 2^(2*n-1)
最初のセットの場合を除く)シフトアンドアドの後のビットが2^n
ビットである可能性があります)。したがって、タイプの幅がに比べて大きい場合n
、これには多くのシフトが必要になります。128ビットタイプを検討してください。n = 3
大きいk
場合は、40シフト以上が必要になります。必要なシフト数を減らすために、次の事実を利用できます。
2^(m*n) - 1 = (2^n - 1) * (2^((m-1)*n) + 2^((m-2)*n) + ... + 2^(2*n) + 2^n + 1),
そのうち、すべてに2^n - 1
分割するものだけを使用します。次に、その倍数だけシフトします。これは、そのステップで値が持つことができる最大ビット長の約半分です。上記の128ビットタイプの例と7()を法とする剰余の場合、3から128/2の最も近い倍数は63と66であり、最初に63ビットシフトします。2^(m*n) - 1
m > 0
n
2^3 - 1
r_1 = (k & (2^63 - 1)) + (k >> 63) // r_1 < 2^63 + 2^(128-63) < 2^66
最大66ビットの数値を取得するには、66/2=33ビットシフトします。
r_2 = (r_1 & (2^33 - 1)) + (r_1 >> 33) // r_2 < 2^33 + 2^(66-33) = 2^34
最大34ビットに到達します。次に18ビットシフトし、次に9、6、3
r_3 = (r_2 & (2^18 - 1)) + (r_2 >> 18) // r_3 < 2^18 + 2^(34-18) < 2^19
r_4 = (r_3 & (2^9 - 1)) + (r_3 >> 9) // r_4 < 2^9 + 2^(19-9) < 2^11
r_5 = (r_4 & (2^6 - 1)) + (r_4 >> 6) // r_5 < 2^6 + 2^(11-6) < 2^7
r_6 = (r_5 & (2^3 - 1)) + (r_5 >> 3) // r_6 < 2^3 + 2^(7-3) < 2^5
r_7 = (r_6 & (2^3 - 1)) + (r_6 >> 3) // r_7 < 2^3 + 2^(5-3) < 2^4
十分であれば、1回の減算になりますr_7 >= 2^3 - 1
。k % (2^n -1)
bビットタイプで計算するには、O(log 2(b / n))シフトが必要です。
商も同様に得られます。
k = a + 2^n*b, 0 <= a < 2^n
= a + ((2^n-1) + 1)*b
= (2^n-1)*b + (a+b),
そうk/(2^n-1) = b + (a+b)/(2^n-1)
、そして私たちはしながら続けa+b > 2^n-1
ます。ここでは残念ながら、幅の約半分をシフトしてマスキングしても作業を減らすことができないため、この方法はn
、タイプの幅よりもそれほど小さくない場合にのみ効率的です。
n
小さすぎない高速ケースのコード:
unsigned long long modulus_2n1(unsigned n, unsigned long long k) {
unsigned long long mask = (1ULL << n) - 1ULL;
while(k > mask) {
k = (k & mask) + (k >> n);
}
return k == mask ? 0 : k;
}
unsigned long long quotient_2n1(unsigned n, unsigned long long k) {
unsigned long long mask = (1ULL << n) - 1ULL, quotient = 0;
while(k > mask) {
quotient += k >> n;
k = (k & mask) + (k >> n);
}
return k == mask ? quotient + 1 : quotient;
}
が型の半分の幅である特殊なケースでn
は、ループは最大2回実行されるため、分岐が高価な場合は、ループを展開して無条件にループ本体を2回実行する方がよい場合があります。