我知道如何处理二的幂,所以这不是我的问题。
例如,如果我想使用位移而不是整数除法来找到一个数的5%,该怎么计算?
因此,我可以使用(x * 100 >> 11)代替(x * 20 / 19).现在这不正确,但它接近,并且我是通过试验和错误得出的。 如何确定要使用的最精确移位?
我知道如何处理二的幂,所以这不是我的问题。
例如,如果我想使用位移而不是整数除法来找到一个数的5%,该怎么计算?
因此,我可以使用(x * 100 >> 11)代替(x * 20 / 19).现在这不正确,但它接近,并且我是通过试验和错误得出的。 如何确定要使用的最精确移位?
最好的方法是让编译器替你完成。你只需写:
a/b
使用您选择的编程语言,编译器将生成位操作。
编辑(希望您不介意,我在加强您的答案:
#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
因此,该过程为
这没有意义,因为你试图做的并没有优化结果的过程!!!
嘿,我在你的问题中没有看到你有意愿进行优化。
电气工程师永远不会停止好奇心,无论“有用性”如何。我们就像强迫性的沉迷者,他们收集各种物品,你可以在新闻中读到他们把阁楼、地下室、卧室和客厅都堆满了垃圾,他们相信某一天这些东西会有用。至少在我30年前上工程学院时是这样的。我鼓励你继续探索收集“无用”的知识,即使它似乎对优化你的生活或生活方式几乎没有可能性。为什么要依赖编译器,当你可以通过手写算法来完成呢?!是吗?要有一点冒险精神,你知道的。 好了,够了,别再批评那些对你追求知识表示轻蔑的人了。
还记得你在初中时学习除法的方法吗?例如437/24。
_____
24|437
018
-----
24|437
24
-----
197
24
-----
5
___________
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.
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。
我希望上面的二进制操作没有错误或笔误。如果有的话,请多多包涵。
a = b / c
。如hroptatyr所提到的,乘法非常快(比除法快得多)。因此,基本思路是将除法转换为乘法,如下:a = b * (1/c)
。1/c
,因此这只适用于已知c
的情况。虽然对于浮点运算来说足够了,但对于整数运算,我们必须使用另一个技巧:我们可以使用值some_big_number / c
的倒数作为c
的倒数,这样最终我们将计算a2 = b * (some_big_number / c)
,它等于some_big_number * b/c
。因为我们关心的是b/c
的值,所以必须将最终结果除以some_big_number
。如果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
以向零舍入)来选择除法的任何舍入方法。
int five_percent(int x) {
return x / 20;
}
g++ -O2
编译此函数时,它不会进行实际的除法运算,而是进行一些神奇的乘法、位移和校正。x * 0xAAAAAAAB
,除以6可以表示为(x * 0xAAAAAAAB) >> 1
,除以12需要移2位,24需要移3位等(它是几何级数3 * (2 ^ x)
,其中0 <= x < 32)一般而言:
<< 2
l
的情况:a * l = a * (l - 1) + a
,现在 l - 1
是偶数,因此可以分解为一个二次幂,对其应用位移“技巧”。除法可以类似地构造。
<<2
的成本。这里的目标是使用一到两个指令乘以任何有理数,而不是分解数字并使用无限数量的指令。 - Potatoswatterimul
需要3个周期,而我的解决方案使用shl
和add
只需要2个周期。 - hroptatyrshl
和一个 add
只能实现乘以5的操作。你仍然需要另一个指令来再次进行移位。编译器应该足够聪明,能够判断出是否真的需要 imul
指令,如果不需要就不生成它,尽管为了可移植性,它可能没有针对你的芯片进行特殊优化,而更高的指令计数可能会导致其他拥堵。 - Potatoswatter
x * 100 >> 11
是x * 100 / 2048
,即x * .048828125
,这是对5%的合理近似。如brainjam所指出的那样,x * 102 >> 11
更好,但是x * 51 >> 10
同样好且更不容易溢出,而x * 205 >> 12
的误差要小得多。 - Ben Voigt