Tra*_*acy 8 algorithm assembly cpu-architecture integer-arithmetic
上周我接受了采访,有一个这样的测试:
使用SHIFT LEFT,SHIFT RIGHT,ADD,SUBSTRACT指令计算N/9
(给定N
为正整数)
.
您可以使用定点数学技巧。
因此,您只需按比例放大,使有效分数部分进入整数范围,进行所需的小数数学运算,然后按比例缩小。
a/9 = ((a*10000)/9)/10000
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,我缩放了10000
. 现在 的整数部分10000/9=1111
足够大,所以我可以写:
a/9 = ~a*1111/10000
Run Code Online (Sandbox Code Playgroud)2 的幂次方
如果你使用 2 的幂,那么你只需要使用位移而不是除法。您需要在精度和输入值范围之间进行折衷。我凭经验发现,在32 bit
算术上,最佳尺度是这样1<<18
的:
(((a+1)<<18)/9)>>18 = ~a/9;
Run Code Online (Sandbox Code Playgroud)
将(a+1)
舍入误差纠正回正确的范围。
硬编码乘法
将乘法常数重写为二进制
q = (1<<18)/9 = 29127 = 0111 0001 1100 0111 bin
Run Code Online (Sandbox Code Playgroud)
现在,如果您需要计算,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;
}
Run Code Online (Sandbox Code Playgroud)
现在,如果您看到二进制形式,则该模式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;
}
Run Code Online (Sandbox Code Playgroud)