【问题标题】:How to divide by 9 using just shifts/add/sub?如何仅使用移位/加/减除以 9?
【发布时间】:2016-07-07 11:21:54
【问题描述】:

上周我在面试,有一个这样的测试:

计算N/9(假设N 是一个正整数),仅使用 左移、右移、加、减指令。

【问题讨论】:

标签: algorithm assembly cpu-architecture integer-arithmetic


【解决方案1】:

首先,找到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

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

【讨论】:

  • 您正在使用@Tracy 尚未批准的循环和条件跳转。在当前的 OP 状态下,它们不会被使用。
  • 查看我对关于 templatetypedef 已删除答案的问题的评论,以及他/她的回复:(1/16) + (1/32) + ... 中的缺陷是整数除法总是向下舍入。
  • @PeterCordes 除以 9 时,在除法之前的除数 n 上加 5 将“四舍五入”(好吧,它是 0.5555 而不是 0.5)... (1/16 + 1/32 + 1/64 + ...) 方法似乎只在 n 上是错误的,其中 n 是 9 的倍数,因此将 1 加到 n 应该有效(即在除法之前)
【解决方案2】:
  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),请使用硬编码二进制乘法:对于q 中的每个1,您可以将a&lt;&lt;(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;
        }
    

【讨论】:

    猜你喜欢
    • 2021-11-16
    • 1970-01-01
    • 2011-02-16
    • 2011-07-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-24
    • 2010-10-07
    相关资源
    最近更新 更多