我知道如何为2的权力做到这一点,所以这不是我的问题。
例如,如果我想使用位移而不是整数除法找到5%的数字,我将如何计算?
所以代替(x * 20/19),我可以做(x * 100>> 11)。现在这不对,但它很接近,我通过反复试验来到它。我如何确定最可能使用的精确班次?
答案 0 :(得分:21)
最好的方法是让编译器为你做。你只需写
a/b
用您选择的语言,编译器生成位twiddling。
编辑(我希望你不介意,我正在为你的答案添加强化:
#include <stdio.h>
int main(int argc, char **argv) {
printf("%d\n", argc/4);
}
显然,最快的事情是argc>>2
。让我们看看会发生什么:
.file "so3.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl 8(%ebp), %eax
movl %eax, %edx
sarl $31, %edx
shrl $30, %edx
leal (%edx,%eax), %eax
sarl $2, %eax
movl %eax, %edx
movl $.LC0, %eax
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
是的,就是sarl $2, %eax
编辑2 (很抱歉继续,但20/19
有点复杂......)
我刚刚将argc*20/19
替换为argc/4
,这就是出来的数学:
0000000100000f07 shll $0x02,%edi
0000000100000f0a movl $0x6bca1af3,%edx
0000000100000f0f movl %edi,%eax
0000000100000f11 imull %edx
0000000100000f13 sarl $0x03,%edx
0000000100000f16 sarl $0x1f,%edi
0000000100000f19 subl %edi,%edx
所以,这个过程是
答案 1 :(得分:8)
这没有任何意义,因为你要做的事情不会优化最终的过程!!!
嘿,我没有在你的问题中随处阅读你有意优化的内容。
无论“有用性”如何,电气工程人员永远不会停止好奇。我们就像你在新闻中读到的强迫性强迫性囤积者,他们将阁楼,酒窖,卧室和起居室堆放起来,他们认为垃圾有时会派上用场。至少在我不到30年前在英格兰学校就读的情况就是如此。我鼓励你继续寻求囤积“无用”的知识,这些知识几乎没有优化你的生活或生活方式的可能性。为什么依靠编译器,你可以通过手工编码算法来做到这一点?!呀?有点冒险,你知道。好吧,让那些对你的知识表达不屑的人不屑一顾。回想一下你的中学,你被教导做分工的方式? 437/24,例如
_____
24|437
018
-----
24|437
24
-----
197
24
-----
5
受分割的数字437称为被除数。 24是除数,结果18是商,5是余数。 就像你提交税款一样,你需要填写你从股票“股息”中获得的利润,这是一个用词不当。你在税表中填写的是一大笔股息的商数的倍数。您没有收到股息,但是没有收到股息 - 否则,这意味着您拥有100%的股票。
___________
11000|110110101
000010010
-----------
11000|110110101
11000
----------
000110101 remainder=subtract divisor from dividend
11000000 shift divisor right and append 0 to quotient until
1100000 divisor is not greater than remainder.
110000 Yihaa!
----------
000101 remainder=subtract shifted divisor from remainder
11000 shift divisor right and append 0 to quotient until
1100 divisor is not greater than remainder.
----------
oops, cannot shift anymore.
以上,正如您可能已经知道的那样,是真正的分裂。这是通过用移位的除数减去来实现的。
你想要的是通过简单地转移股息来实现同样的目标。不幸的是,除非除数是2(2,4,8,16)的指数幂,否则不能这样做。这是二进制算术的一个明显事实。或者,至少我不知道任何方法可以在没有近似和插值技术的情况下做到这一点。
因此,您必须结合使用红利和真正的分工。 e.g。
24 = 2 x 2 x 2 x 3
首先,使用二进制移位将437除以8得到010010,然后使用真除除除以3:
010010
--------
11|110110
11
-------
011
11
-----
0
可用于010010 = 18。
瞧。
如何确定24 = 2 ^ 8 x 3?
向右移动11000直到你达到1。
这意味着,您可以将被除数与移动除数的次数相同,直到除数达到1为止。
因此,显然,如果除数是奇数,这种方法就不起作用。 例如,它对除数25不起作用,但它对除数50有效。
可能是,有预测方法可以将像13这样的除数插入到2 ^ 3 = 8和2 ^ 4 = 16之间。如果有,我不熟悉它们。
您需要探索的是使用数字系列。例如除以25:
1 1 1 1 1
__ = __ - ___ - ___ + ___ - ... until the precision you require.
25 16 64 128 256
该系列的一般形式是
1 1 b1 bn
_ = ___ + _______ + ... + ______
D 2^k 2^(k+1) 2^(k+n)
其中bn为-1,0或+1。
我希望上面的二进制操作不会有错误或拼写错误。如果是这样,千万道歉。
答案 2 :(得分:6)
假设您有表达式a = b / c
。正如hroptatyr所提到的,乘法非常快(并且它比分裂快得多)。因此,基本思想是将划分转换为乘法,如:a = b * (1/c)
。
现在,我们仍然需要划分计算收件人1/c
,所以这只有在c
已知的情况下才有效。虽然对于浮点计算来说已经足够了,但对于交互,我们必须使用另一个技巧:我们可以使用值c
的值some_big_number / c
的倒数,这样我们最终将计算a2 = b * (some_big_number / c)
},等于some_big_number * b/c
。因为我们对b/c
的价值感兴趣,所以我们必须将最终结果除以some_big_number
。如果它被选择为2的幂,那么最后的分裂将很快。
例如:
// we'll compute 1/20 of the input
unsigned divide_by_20(unsigned n){
unsigned reciprocal = (0x10000 + 20 - 1) / 20; //computed at compile time, but you can precompute it manually, just to be sure
return (n * reciprocal) >> 16;
}
编辑:这种方法的一个很好的部分是你可以通过选择校正来选择任何舍入方法(在这种情况下,它是20 - 1
向零舍入)。
答案 3 :(得分:6)
如果您对其背后的数学感兴趣,请阅读Henry S. Warren的 Hacker's Delight 。
如果您对优化代码感兴趣,只需编写人类最容易阅读的内容。例如:
int five_percent(int x) {
return x / 20;
}
使用g++ -O2
编译此函数时,它不会进行实际除法,而是进行一些魔法乘法,位移和校正。
答案 4 :(得分:3)
你不能用轮班做任何事情,你需要使用'魔术'除数(见黑客喜悦)。魔术师通过将一个数字乘以另一个适当大的数字来进行工作,将其翻过来产生除法的答案(mul / imul比div / idiv更快)。魔术常数对于每个素数都是唯一的,倍数需要移位,例如:无符号除以3可以表示(在32位上)为x * 0xAAAAAAAB
,除以6将(x * 0xAAAAAAAB) >> 1
除以12将移动2,24乘3等(其几何系列3 * (2 ^ x)
,其中0 <= x <32)
答案 5 :(得分:2)
假设您希望通过乘以y并移位n来近似x的5%。由于5%是1/20,并且&gt;&gt; n = a / 2 n ,您想解决
x /20≈x* y / 2 n (符号“≈”表示“近似相等”)
简化为
y≈2 n / 20
所以如果n = 11,那么
y≈2 n / 20 = 2048/20 = 102 + 8/20
所以我们可以设置y = 102,这实际上比你通过反复试验找到的100更好。
一般来说,我们可以和n一起玩,看看我们是否能得到更好的答案。
我已经为分数1/20做了这个,但你应该能够通过遵循相同的方法来解决任何分数p / q。
答案 6 :(得分:0)
一般情况下:
<< 2
l
观察以下内容:a * l = a * (l - 1) + a
,现在l - 1
是偶数,因此分解成两个幂,对于该位转移'技巧'适用。分区可以类似地构建。