ビット演算による割り算の解説

に公開

ビット演算による割り算」に関して、個人的に納得のいく解説が見つからなかったため、自分なりの理解を書き留めます。

以下のような、典型的なコーディングテストの問題を解くためにも必要な知識です。
https://leetcode.com/problems/divide-two-integers

筆算 (10進数) から考える

具体的に、2025を71で割るケースを考えます。
小学校で習う筆算を使うと、以下のようになり、商が28、余りが37と分かります。

深く考えず手続きとして覚えている人も多いと思いますが、筆算が実際に何をしているかというと、以下の式における「2」「8」を探す作業を行なっています。

2025 = (2 \times 71 \times 10^{1}) + (8 \times 71 \times 10^{0}) + 37

以下のようなステップで、その作業が行われています。

  1. 202571 \times 10^{2}で割る。答えは0なので、そのまま次へ。
  2. 202571 \times 10^{1}で割る。答えが22025から2 \times 71 \times 10^{1}を引いて、605を得る。
  3. 60571 \times 10^{0}で割る。答えが8605から8 \times 71 \times 10^{0}を引いて、37 (余り) を得る。

2進数で考える

一般的な筆算の場合には10進数ベースで行なわれますが、これを2進数ベースで行うことも可能です。
以下の式における、「1」「1」「1」「0」「0」を探す作業となります。

2025 = (1 \times 71 \times 2^4) + (1 \times 71 \times 2^3) + (1 \times 71 \times 2^2) + (0 \times 71 \times 2^1) + (0 \times 71 \times 2^0) + 37
  1. 202571 \times 2^{5}で割る。答えは0なので、そのまま次へ。
  2. 202571 \times 2^{4}で割る。答えは12025から1 \times 71 \times 2^{4}を引いて、889を得る。
  3. 88971 \times 2^{3}で割る。答えは1889から1 \times 71 \times 2^{3}を引いて、321を得る。
  4. 32171 \times 2^{2}で割る。答えは1321から1 \times 71 \times 2^{2}を引いて、37を得る。
  5. 3771 \times 2^{1}で割る。答えは0なので、そのまま次へ。
  6. 3771 \times 2^{0}で割る。答えは037が余りとなる。

この時、各ステップで得られた011100 (10進数表記で28) が答えとなります。

アルゴリズムへと一般化する

前セクションの具体例を一般化して、アルゴリズムに落とし込みます。
割り算を、N = DQ + Rにおける、商Qと余りR (0 \leq R < D) を求める問題とします。

すると、前セクションの具体例と同様に、以下のようなステップで割り算を行うことが可能です。

  1. N_{i}D \times 2^{i}で割る。その答えをd_{i}とする。N_{i}からd_{i} \times D \times 2^{i}を引いて、N_{i-1}を得る。
  2. 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}を得る。
  3. N_{i-k}が0になるまで続ける。

十分に大きなiから始めることで、商をd_{i}, d_{i-1}, ..., d_{0}として得ることが可能です。

アルゴリズムをコードにする

前セクションのアルゴリズムをコードにします。一例として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