Division without division

Time:2020-11-27

preface

At the beginning of July, we challenged leetcode‘s question 29 (medium difficulty, which seems nothing to boast about). The problem requires that the function of integer division be realized without division, multiplication and modular operation.

Since the divisor and divisor are integers, subtraction can be used to achieve division (what a naive idea). A trivial function written in JavaScript can be as follows (for simplicity, only consider the case where both parameters are positive integers)

function divide(n, m) {
  let acc = 0;
  while (n >= m) {
    n -= m;
    acc += 1;
  }
  return acc;
}

So simpledivideFunction submission to leetcode is not accepted – it will time out on test cases like 2147483648 divided by 2. You can run it locally and feel how slow it is

➜  nodejs time node divide.js
2147483648/2=1073741824
node divide.js  1.14s user 0.01s system 99% cpu 1.161 total

Is there a faster algorithm to calculate the quotient of two integers? The answer, of course, is yes.

Try to optimize

It can be seen at a glance that the most frequent runs are among themwhileCycle. Take 2147483648 divided by 2,whileStatements in the loop are executed 1073741824 times. In order to increase the running speed, the number of cycles must be reduced.

Since every timenSubtract frommIt needs to be implementedn/mTimes, then if you subtract each time2mNo, it just needs to be executed(n/m)/2Have you done that? The number of cycles is reduced by half. I’m excited to think about it. Every time2mAnd the code and running effect of the algorithm are as follows

➜  nodejs cat divide2.js
function divide(n, m) {
  let acc = 0;
  Let M2 = m < < 1; // since multiplication is not allowed for the title, the left shift is used instead of multiplying by 2.
  while (n >= m2) {
    n -= m2;
    acc += 2;
  }
  while (n >= m) {
    n -= m;
    acc += 1;
  }
  return acc;
}

console.log(`2147483648/2=${divide(2147483648, 2)}`);
➜  nodejs time node divide2.js
2147483648/2=1073741824
node divide2.js  2.65s user 0.01s system 99% cpu 2.674 total

Although the time consumption does not decrease but rises, which makes the scene very embarrassing, according to theoretical analysis, the first cycle runs only half of the original number, while the second cycle runs at most once, which shows that the optimization direction is OK.

If the calculationm2The number of times to move left is 2accThe number of the first cycle will be greatly reduced to 268435456, and the number of the second cycle will not exceed 4; if the number of times of left shift is 3, thenaccThe number of the first cycle decreases to 134217728 and the number of the second cycle does not exceed 8.

Obviously, the left shift cannot go on indefinitely becausem2Sooner or later, the value ofn。 It’s easy to calculate that the upper limit of the number of left shifts is

Division without division

Logarithmic notation means that even for very largenAnd very small onesmThe result of the above formula is not very large, so it can significantly improve the efficiency of integer division.

Before I start to write the code, let me briefly prove the quotient and direct calculation of this methodn/mThey are equal.

A simple proof

Count the number to be subtractedn, the subtraction ism。 Obviously, there is a positive integerN, so that

Division without division

order

Division without division

, and then

Division without division

, sondividemEquivalent to

Division without division

The proof is complete.

From the above formula, we can also know that the new algorithm will have the original scale of 0nThe problem is transformed into a scale ofrThis means that the final code can be written gracefully recursively.

Complete code

FinaldivideThe code for the function is as follows

function divide(n, m) {
  if (n < m) {
    return 0;
  }

  let n2 = n;
  let N = 0;
  //Use right shift instead of left shift to avoid overflow.
  while ((n2 >> 1) > m) {
    N += 1;
    n2 = n2 >> 1;
  }

  //'power' denotes the nth power of 2 in the formula
  //'product' represents the product of 'power' and the divisor'm '
  let power = 1;
  let product = m;
  for (let i = 0; i < N; i++) {
    power = power << 1;
    product = product << 1;
  }
  return power + divide(n - product, m);
}

This is comparable to the beginningdivideIt’s much faster. There’s a picture, there’s a truth

➜  nodejs time node divide3.js
2147483648/2=1073741824
node divide3.js  0.03s user 0.01s system 95% cpu 0.044 total

Postscript

IfT(n, m)Indicates that the dividend isn, divisor ismThen its recursive formula can be written in the following form

Division without division

But this thing doesn’t seem to be able to work out the analytic formula directly from the main theorem, so unfortunately, I don’t know what the time complexity of this algorithm is – though I guess it isNThe calculation formula of.

If there is any kind reader friend who knows about it, I hope you can give me some advice.

Read the original