上周我接受了采访,并进行了类似的测试:
计算N/9
(假设N
是正整数),仅使用
左移,右移,添加,替换说明。
答案 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)
您可以使用定点数学技巧。
所以你只需按比例放大,使重要部分变为整数范围,进行所需的分数数学运算并缩小。
a/9 = ((a*10000)/9)/10000
你可以看到我按10000
缩放。现在10000/9=1111
的整数部分足够大,所以我可以写:
a/9 = ~a*1111/10000
2比例的力量
如果你使用2比例的幂,那么你只需要使用位移而不是除法。您需要在精度和输入值范围之间进行折衷。我凭经验发现,在32 bit
算术上,最佳比例是1<<18
所以:
(((a+1)<<18)/9)>>18 = ~a/9;
(a+1)
将舍入错误更正回到正确的范围。
硬编码乘法
将乘法常数重写为二进制
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;
}