如何使用shift / add / sub除以9?

时间:2016-03-21 03:39:07

标签: algorithm assembly cpu-architecture integer-arithmetic

上周我接受了采访,并进行了类似的测试:

计算N/9(假设N是正整数),仅使用 左移,右移,添加,替换说明。

2 个答案:

答案 0 :(得分:2)

首先,找到二进制的1/9表示 0,0001110001110001
意味着它(1/16)+(1/32)+(1/64)+(1/1024)+(1/2048)+(1/4096)+(1/65536) 所以(x / 9)等于(x>> 4)+(x>> 5)+(x>>> 6)+(x>> 10)+( x>> 11)+(x>> 12)+(x>> 16)

可能的优化(如果允许循环):
如果你循环遍历0001110001110001b,将每个循环右移,
添加" x"每当在此班次设置进位时,到结果寄存器 并且每次都改变你的结果, 你的结果是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

(目前无法测试此,可能是错误的)

答案 1 :(得分:1)

  1. 您可以使用定点数学技巧。

    所以你只需按比例放大,使重要部分变为整数范围,进行所需的分数数学运算并缩小。

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

    你可以看到我按10000缩放。现在10000/9=1111的整数部分足够大,所以我可以写:

    a/9 = ~a*1111/10000
    
  2. 2比例的力量

    如果你使用2比例的幂,那么你只需要使用位移而不是除法。您需要在精度和输入值范围之间进行折衷。我凭经验发现,在32 bit算术上,最佳比例是1<<18所以:

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

    (a+1)将舍入错误更正回到正确的范围。

  3. 硬编码乘法

    将乘法常数重写为二进制

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

    现在,如果您需要计算c=(a*q)使用硬编码二进制乘法:对于1的每个q,您可以将a<<(position_of_1)添加到c 。如果您看到111之类的内容,则可以将其重写为1000-1,从而最大限度地减少操作次数。

    如果你将所有这些放在一起,你应该得到类似我的 C ++ 代码:

    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;
        }
    

    现在,如果你看到二进制形式,模式111000正在重复,那么你可以进一步改进代码:

    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;
        }