➗
ビット演算による割り算の解説
「ビット演算による割り算」に関して、個人的に納得のいく解説が見つからなかったため、自分なりの理解を書き留めます。
以下のような、典型的なコーディングテストの問題を解くためにも必要な知識です。
筆算 (10進数) から考える
具体的に、2025を71で割るケースを考えます。
小学校で習う筆算を使うと、以下のようになり、商が28、余りが37と分かります。
深く考えず手続きとして覚えている人も多いと思いますが、筆算が実際に何をしているかというと、以下の式における「2」「8」を探す作業を行なっています。
以下のようなステップで、その作業が行われています。
-
を2025 で割る。答えは71 \times 10^{2} なので、そのまま次へ。0 -
を2025 で割る。答えが71 \times 10^{1} 。2 から2025 を引いて、2 \times 71 \times 10^{1} を得る。605 -
を605 で割る。答えが71 \times 10^{0} 。8 から605 を引いて、8 \times 71 \times 10^{0} (余り) を得る。37
2進数で考える
一般的な筆算の場合には10進数ベースで行なわれますが、これを2進数ベースで行うことも可能です。
以下の式における、「1」「1」「1」「0」「0」を探す作業となります。
-
を2025 で割る。答えは71 \times 2^{5} なので、そのまま次へ。0 -
を2025 で割る。答えは71 \times 2^{4} 。1 から2025 を引いて、1 \times 71 \times 2^{4} を得る。889 -
を889 で割る。答えは71 \times 2^{3} 。1 から889 を引いて、1 \times 71 \times 2^{3} を得る。321 -
を321 で割る。答えは71 \times 2^{2} 。1 から321 を引いて、1 \times 71 \times 2^{2} を得る。37 -
を37 で割る。答えは71 \times 2^{1} なので、そのまま次へ。0 -
を37 で割る。答えは71 \times 2^{0} 。0 が余りとなる。37
この時、各ステップで得られた011100
(10進数表記で
アルゴリズムへと一般化する
前セクションの具体例を一般化して、アルゴリズムに落とし込みます。
割り算を、
すると、前セクションの具体例と同様に、以下のようなステップで割り算を行うことが可能です。
-
をN_{i} で割る。その答えをD \times 2^{i} とする。d_{i} からN_{i} を引いて、d_{i} \times D \times 2^{i} を得る。N_{i-1} -
をN_{i-1} で割る。その答えをD \times 2^{i-1} とする。d_{i-1} からN_{i-1} を引いて、d_{i-1} \times D \times 2^{i-1} を得る。N_{i-2} -
が0になるまで続ける。N_{i-k}
十分に大きな
アルゴリズムをコードにする
前セクションのアルゴリズムをコードにします。一例としてC++コードで表します。
class Solution {
public:
int divide(const int dividend, const int divisor) const {
assert(divisor != 0);
// 符号への対応
bool isMinus = (dividend < 0) ^ (divisor < 0);
long long n = std::abs((long long)dividend);
long long d = std::abs((long long)divisor);
unsigned int result = 0;
// 32bit整数において十分に大きなiとして、31から始める。
for (int i = 31; i >= 0; i--) {
if (n >= (d << i)) { // NiをD * 2^iで割った答えを求める。
result |= (1LL << i); // 答えが1であれば、答えのbitを立てる。
n -= (d << i); // NiからD * 2^iを引いて、次のNi-1を得る。
}
}
// 答えがINT_MAX + 1になってしまうエッジケースのハンドリング
if (!isMinus && result > INT_MAX) {
return INT_MAX;
}
// 符号を付けて返す。
return isMinus ? -result : result;
}
};
Discussion