8

Last week I was in an interview and there was a test like this:

Calculate N/9 (given that N is a positive integer), using only SHIFT LEFT, SHIFT RIGHT, ADD, SUBSTRACT instructions.

Tracy
  • 1,988
  • 5
  • 25
  • 36
  • Can `N` be negative? If so, is the right-shift an arithmetic shift or logical shift? – Peter Cordes Mar 21 '16 at 03:52
  • [Divide by 10 using bit shifts?](http://stackoverflow.com/q/5558492/995714), [Divide a number by 3 without using *, /, +, -, % operators](http://stackoverflow.com/q/11694546/995714), [How can I multiply and divide using only bit shifting and adding?](http://stackoverflow.com/q/2776211/995714), [Implement division with bit-wise operator](http://stackoverflow.com/q/5284898/995714) – phuclv Mar 21 '16 at 03:56
  • 6
    to divide by 9, [multiply by 954437177](http://www.hackersdelight.org/magic.htm) – phuclv Mar 21 '16 at 03:58
  • 1
    @templatetypedef: your deleted answer is nearly right, but the pattern repeats every 6 bits, not every 4 bits. With `L=(1/16+1/32+1/64)`, you get a very close approximation to 1/9 from `L + L/2^6 + L/2^12 + L/2^18 + L/2^24 + L/2^30 == ~0.11111111110949423164`. I just made an edit to fix it, so you should undelete it. I was fumbling around with sums of power-of-two fractions, and would probably have gotten to your answer at some point, but I don't want to type it up since you've already done so. – Peter Cordes Mar 21 '16 at 04:09
  • 3
    @PeterCordes The code actually doesn't work, unfortunately, because all the divisions round down. If you try it on a value like 9 / 9, for example, you get 0 rather than the expected 1. – templatetypedef Mar 21 '16 at 04:14
  • @templatetypedef: ahh, I see. Is there a workaround like adding `1` to the initial value before any shifting? hrm, no it's not that easy. Maybe add 1 to the highest bit-position that will be shifted out, to do a divide with rounding instead of truncation? (So it's like adding `0.5` to non-truncated fixed-point result of the shift, except you actually do it before the shift.) – Peter Cordes Mar 21 '16 at 04:27
  • (Without conditional operations, the "obvious" way to divide is multiply by inverse, as linked in Lưu Vĩnh Phúc's second comment.) – greybeard Mar 21 '16 at 06:00
  • can you use `for` loops and `if`s? you can encode binary division with single `for` loop using `<<,>>,+,-,if (a=9;a-=9) c++;` – Spektre Mar 21 '16 at 08:51
  • @Tracy added answer on 32bit arithmetics valid for inputs up to `147455` including. No loops, using just `+,-,<<,>>` – Spektre Mar 21 '16 at 09:39
  • Given the nature of the answers and the attempts at one, this doesn't look like it's a fair interview question at all. I wonder if the question was supposed to be multiply by 9, which is a lot simpler. – Ross Ridge Mar 21 '16 at 21:33

2 Answers2

2

first, find the representation of 1/9 in binary 0,0001110001110001
means it's (1/16) + (1/32) + (1/64) + (1/1024) + (1/2048) + (1/4096) + (1/65536)
so (x/9) equals (x>>4) + (x>>5) + (x>>6) + (x>>10) + (x>>11)+ (x>>12)+ (x>>16)

Possible optimization (if loops are allowed):
if you loop over 0001110001110001b right shifting it each loop,
add "x" to your result register whenever the carry was set on this shift and shift your result right each time afterwards, your result is x/9

        mov cx, 16     ; assuming 16 bit registers
        mov bx, 7281   ; bit mask of 2^16 * (1/9)
        mov ax, 8166   ; sample value, (1/9 of it is 907)
        mov dx, 0      ; dx holds the result

    div9:
        inc ax         ; or "add ax,1" if inc's not allowed :) 
                       ; workaround for the fact that 7/64 
                       ; are a bit less than 1/9
        shr bx,1
        jnc no_add
        add dx,ax
    no_add:
        shr dx,1
        dec cx
        jnz div9

( currently cannot test this, may be wrong)

Tommylee2k
  • 2,683
  • 1
  • 9
  • 22
  • 1
    you are using loops and conditional jumps which @Tracy has still not approved. In current state of OP they are not to be used. – Spektre Mar 21 '16 at 13:10
  • See my comment on the question about templatetypedef's deleted answer, and his/her reply: The flaw in `(1/16) + (1/32) + ...` is that integer division always rounds down. – Peter Cordes Mar 21 '16 at 19:30
  • 1
    @PeterCordes when dividing by 9, adding 5 to the dividend n prior of division would be "rounding" up (ok, it's 0.5555 not 0.5) ... the (1/16 + 1/32 + 1/64 + ...) approach seems only wrong on n where n is a multiple of 9, so adding 1 to n should work (before dividing, that is) – Tommylee2k Mar 22 '16 at 07:50
1
  1. you can use fixed point math trick.

    so you just scale up so the significant fraction part goes to integer range, do the fractional math operation you need and scale back.

    a/9 = ((a*10000)/9)/10000
    

    as you can see I scaled by 10000. Now the integer part of 10000/9=1111 is big enough so I can write:

    a/9 = ~a*1111/10000
    
  2. power of 2 scale

    If you use power of 2 scale then you need just to use bit-shift instead of division. You need to compromise between precision and input value range. I empirically found that on 32 bit arithmetics the best scale for this is 1<<18 so:

    (((a+1)<<18)/9)>>18 = ~a/9;
    

    The (a+1) corrects the rounding errors back to the right range.

  3. Hardcoded multiplication

    Rewrite the multiplication constant to binary

    q = (1<<18)/9 = 29127 = 0111 0001 1100 0111 bin
    

    Now if you need to compute c=(a*q) use hard-coded binary multiplication: for each 1 of the q you can add a<<(position_of_1) to the c. If you see something like 111 you can rewrite it to 1000-1 minimizing the number of operations.

    If you put all of this together you should got something like this C++ code of mine:

    DWORD div9(DWORD a)
        {
        // ((a+1)*q)>>18 = (((a+1)<<18)/9)>>18 = ~a/9;
        // q = (1<<18)/9 = 29127 = 0111 0001 1100 0111 bin
        // valid for a = < 0 , 147455 >
        DWORD c;
        c =(a<< 3)-(a    ); // c= a*29127
        c+=(a<< 9)-(a<< 6);
        c+=(a<<15)-(a<<12);
        c+=29127;           // c= (a+1)*29127
        c>>=18;             // c= ((a+1)*29127)>>18
        return c;
        }
    

    Now if you see the binary form the pattern 111000 is repeating so yu can further improve the code a bit:

    DWORD div9(DWORD a)
        {
        DWORD c;
        c =(a<<3)-a;        // first pattern
        c+=(c<<6)+(c<<12);  // and the other 2...
        c+=29127;           
        c>>=18;             
        return c;
        }
    
Spektre
  • 49,595
  • 11
  • 110
  • 380