4

I am going through an algorithms and datastructures textbook and came accross this question:

1-28. Write a function to perform integer division without using either the / or * operators. Find a fast way to do it.

How can we come up with a fast way to do it?

vish4071
  • 5,135
  • 4
  • 35
  • 65
Teererai Marange
  • 2,044
  • 4
  • 32
  • 50
  • 1
    evident solution is teached at elementary school: by binary representation: and, or, xor and shift of bits. – guillaume girod-vitouchkina Dec 29 '15 at 08:16
  • By looking for hairs to split in the problem statement: `n/d` doesn't use *, `n*d⁻¹` avoids /. If _both_ shouldn't have been used, the problem could have stated that or read _using neither `/` nor `*`_. – greybeard Dec 29 '15 at 09:58
  • @greybeard - surely "without using either ... or ... " ⇔ "using neither ... nor ...", or do I have split ends? – Joe May 08 '16 at 19:08
  • Depends on the equivalence of _not using either a or b_ to _either not using a or not using b_. – greybeard May 08 '16 at 20:05
  • Possible duplicate of [dividing a number without using division operator in c](https://stackoverflow.com/questions/21074682/dividing-a-number-without-using-division-operator-in-c) – phuclv Jul 06 '18 at 15:25
  • Possible duplicate of [Division without using '/'](https://stackoverflow.com/questions/5386377/division-without-using) – mlunoe Oct 04 '19 at 08:44

7 Answers7

11

I like this solution: https://stackoverflow.com/a/34506599/1008519, but I find it somewhat hard to reason about (especially the |-part). This solution makes a little more sense in my head:

var divide = function (dividend, divisor) {
  // Handle 0 divisor
  if (divisor === 0) {
    return NaN;
  }

  // Handle negative numbers
  var isNegative = false;
  if (dividend < 0) {
    // Change sign
    dividend = ~dividend+1;
    isNegative = !isNegative;
  }

  if (divisor < 0) {
    // Change sign
    divisor = ~divisor+1;
    isNegative = !isNegative;
  }

  /**
   * Main algorithm
   */

  var result = 1;
  var denominator = divisor;
  // Double denominator value with bitwise shift until bigger than dividend
  while (dividend > denominator) {
    denominator <<= 1;
    result <<= 1;
  }

  // Subtract divisor value until denominator is smaller than dividend
  while (denominator > dividend) {
    denominator -= divisor;
    result -= 1;
  }

  // If one of dividend or divisor was negative, change sign of result
  if (isNegative) {
    result = ~result+1;
  }

  return result;
}
  1. We initialize our result to 1 (since we are going to double our denominator until it is bigger than the dividend)
  2. Double our denominator (with bitwise shifts) until it is bigger than the dividend
  3. Since we know our denominator is bigger than our dividend, we can minus our divisor until it is less than our dividend
  4. Return result since denominator is now as close to the result as possible using the divisor

Here are some test runs:

console.log(divide(-16, 3)); // -5
console.log(divide(16, 3)); // 5
console.log(divide(16, 33)); // 0
console.log(divide(16, 0)); // NaN
console.log(divide(384, 15)); // 25

Here is a gist of the solution: https://gist.github.com/mlunoe/e34f14cff4d5c57dd90a5626266c4130

mlunoe
  • 4,422
  • 4
  • 32
  • 43
  • 1
    I aware it's a one-year old answer but a very nice one, especially the effort on explanation. The `c |= index` part in the solution mentioned is equivalent to `c += 1` as `c` was initialized as `0`. – Shawn Xiong Feb 15 '17 at 12:28
  • @MatthewStory I have updated it to display the full solution that I had otherwise just referred to at the bottom, which handles 0 and negative numbers. I hope this helps! – mlunoe Oct 15 '18 at 13:35
  • The second loop is not very different from simply incrementing `result` from 0. Same complexity. The trick in @vish4071 s answer is that it shift the denominator back, i.e. halve it each time, reducing the complexity to lg(N). See this [cleaner example](https://stackoverflow.com/questions/5386377/division-without-using?answertab=scoredesc#tab-top) – Aelian Jan 21 '23 at 08:00
2

Typically, when an algorithms textbook says fast they mean in terms of computational complexity. That is, the number of operations per bit of input. In general, they don't care about constants, so if you have an input of n bits, whether it takes two operations per bit or a hundred operations per bit, we say the algorithm takes O(n) time. This is because if we have an algorithm that runs in O(n^2) time (polynomial... in this case, square time) and we imagine a O(n) algorithm that does 100 operations per bit compared to our algorithm which may do 1 operation per bit, once the input size is 100 bits, the polynomial algorithm starts to run really slow really quickly (compared to our other algorithm). Essentially, you can imagine two lines, y=100x and y=x^2. Your teacher probably made you do an exercise in Algebra (maybe it was calculus?) where you have to say which one is bigger as x approaches infinity. This is actually a key concept in divergence/convergence in calculus if you have gotten there already in mathematics. Regardless, with a little algebra, you can imagine our graphs intersecting at x=100, and y=x^2 being larger for all points where x is greater than 100.

As far as most textbooks are concerned, O(nlgn) or better is considered "fast". One example of a really bad algorithm to solve this problem would be the following:

crappyMultiplicationAlg(int a, int b)
    int product = 0
    for (b>0)
        product = product + a
        b = b-1
    return product

This algorithm basically uses "b" as a counter and just keeps adding "a" to some variable for each time b counts down. To calculate how "fast" the algorithm is (in terms of algorithmic complexity) we count how many runs different components will take. In this case, we only have a for loop and some initialization (which is negligible in this case, ignore it). How many times does the for loop run? You may be saying "Hey, guy! It only runs 'b' times! That may not even be half the input. Thats way better than O(n) time!"

The trick here, is that we are concerned with the size of the input in terms of storage... and we all (should) know that to store an n bit integer, we need lgn bits. In other words, if we have x bits, we can store any (unsigned) number up to (2^x)-1. As a result, if we are using a standard 4 byte integer, that number could be up to 2^32 - 1 which is a number well into the billions, if my memory serves me right. If you dont trust me, run this algorithm with a number like 10,000,000 and see how long it takes. Still not convinced? Use a long to use a number like 1,000,000,000.

Since you didn't ask for help with the algorithm, Ill leave it for you as a homework exercise (not trying to be a jerk, I am a total geek and love algorithm problems). If you need help with it, feel free to ask! I already typed up some hints by accident since I didnt read your question properly at first.

EDIT: I accidentally did a crappy multiplication algorithm. An example of a really terrible division algorithm (i cheated) would be:

AbsolutelyTerribleDivisionAlg(int a, int b)
    int quotient = 0
    while crappyMultiplicationAlg(int b, int quotient) < a
        quotient = quotient + 1
    return quotient

This algorithm is bad for a whole bunch of reasons, not the least of which is the use of my crappy multiplication algorithm (which will be called more than once even on a relatively "tame" run). Even if we were allowed to use the * operator though, this is still a really bad algorithm, largely due to the same mechanism used in my awful mult alg.

PS There may be a fence-post error or two in my two algs... i posted them more for conceptual clarity than correctness. No matter how accurate they are at doing multiplication or division, though, never use them. They will give your laptop herpes and then cause it to burn up in a sulfur-y implosion of sadness.

botch
  • 167
  • 1
  • 10
1

I don't know what you mean by fast...and this seems like a basic question to test your thought process.

A simple function can be use a counter and keep subtracting the divisor from the dividend till it becomes 0. This is O(n) process.

int divide(int n, int d){
    int c = 0;
    while(1){
        n -= d;
        if(n >= 0)
            c++;
        else
            break;
    }
    return c;
}

Another way can be using shift operator, which should do it in log(n) steps.

    int divide(int n, int d){
    if(d <= 0)
        return -1;
    int k = d;
    int i, c, index=1;
    c = 0;
    while(n > d){
        d <<= 1;
        index <<= 1;
    }
    while(1){
        if(k > n)
            return c;
        if(n >= d){
            c |= index;
            n -= d;                
        }
        index >>= 1;
        d >>= 1;
    }
    return c;
}

This is just like integer division as we do in High-School Mathematics.

PS: If you need a better explanation, I will. Just post that in comments.

EDIT: edited the code wrt Erobrere's comment.

vish4071
  • 5,135
  • 4
  • 35
  • 65
1

The simplest way to perform a division is by successive subtractions: subtract b from a as long as a remains positive. The quotient is the number of subtractions performed.

This can be pretty slow, as you will perform q subtractions and tests.

With a=28 and b=3,

28-3-3-3-3-3-3-3-3-3=1

the quotient is 9 and the remainder 1.

The next idea that comes to mind is to subtract several times b in a single go. We can try with 2b or 4b or 8b... as these numbers are easy to compute with additions. We can go as for as possible as long as the multiple of b does not exceed a.

In the example, 2³.3 is the largest multiple which is possible

28>=2³.3

So we subtract 8 times 3 in a single go, getting

28-2³.3=4

Now we continue to reduce the remainder with the lower multiples, , 2 and 1, when possible

4-2².3<0
4-2.3 <0
4-1.3 =1

Then our quotient is 2³+1=9 and the remainder 1.

As you can check, every multiple of b is tried once only, and the total number of attempts equals the number of doublings required to reach a. This number is just the number of bits required to write q, which is much smaller than q itself.

0
unsigned bitdiv (unsigned a, unsigned d)
{
unsigned res,c;

for (c=d; c <= a; c <<=1) {;}

for (res=0;(c>>=1) >= d; ) {
        res <<= 1;
        if ( a >= c) { res++; a -= c; }
        }
return res;
}
joop
  • 4,330
  • 1
  • 15
  • 26
0

This is not the fastest solution, but I think it's readable enough and works:

def weird_div(dividend, divisor):
    if divisor == 0:
        return None

    dend = abs(dividend)
    dsor = abs(divisor)
    result = 0
    
    # This is the core algorithm, the rest is just for ensuring it works with negatives and 0
    while dend >= dsor:
        dend -= dsor
        result += 1
    
    # Let's handle negative numbers too
    if (dividend < 0 and divisor > 0) or (dividend > 0 and divisor < 0):
        return -result
    else:
        return result

# Let's test it:
print("49 divided by 7 is {}".format(weird_div(49,7)))
print("100 divided by 7 is {} (Discards the remainder) ".format(weird_div(100,7)))
print("-49 divided by 7 is {}".format(weird_div(-49,7)))     
print("49 divided by -7 is {}".format(weird_div(49,-7)))   
print("-49 divided by -7 is {}".format(weird_div(-49,-7)))   
print("0 divided by 7 is {}".format(weird_div(0,7)))         
print("49 divided by 0 is {}".format(weird_div(49,0)))

It prints the following results:

49 divided by 7 is 7
100 divided by 7 is 14 (Discards the remainder) 
-49 divided by 7 is -7
49 divided by -7 is -7
-49 divided by -7 is 7
0 divided by 7 is 0
49 divided by 0 is None
-1

The pseudo code:

count = 0
while (dividend >= divisor) 
    dividend -= divisor
    count++
//Get count, your answer
  • Sorry, but this is a poor answer. It’s not a fast method; another answer covers this method better; this answer is just code, with no explanation. Please review the help files for guidance. – Tom Zych Dec 29 '15 at 08:52
  • @Tom Zych: why is this not a fast method? It is O(n) in bits. Frankly, it's a better answer than the others. – stackoverflowuser2010 Dec 29 '15 at 09:23
  • @stackoverflowuser2010: O(n) in bits? Really? Have you checked that? Use it to divide `2**16` by `2**4`. Does it take 16 steps? No, it takes `2**12` steps. In fact, in all cases the number of iterations is precisely equal to the answer, as the code makes very clear. – Tom Zych Dec 29 '15 at 09:27
  • @TomZych: My mistake, it's O(n) where n is the numerator. – stackoverflowuser2010 Dec 29 '15 at 19:11