在C中使用移位运算符的乘法和除法实际上更快吗

Is multiplication and division using shift operators in C actually faster?

本文关键字:除法 实际上 运算符      更新时间:2023-10-16

乘法和除法可以使用位运算符实现,例如

i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)

等等

(i<<3)+(i<<1)乘10比直接用i*10快吗?有没有什么输入不能用这种方式相乘或除法?

简短回答:不太可能。

长话短说:编译器中有一个优化器,它知道如何尽可能快地进行乘法运算。你最好的办法是清楚地告诉编译器你的意图(即i*2而不是i<<1),并让它决定什么是最快的汇编/机器代码序列;添加了微码。

最重要的是,不要花太多时间担心这件事。如果你想改变,就改变。如果你想乘,那就乘。做语义最清晰的事情——你的同事稍后会感谢你的。或者,更可能的是,如果你不这样做,以后诅咒你。

只是一个具体的衡量点:多年前,我以两个我的哈希算法版本:

unsigned
hash( char const* s )
{
unsigned h = 0;
while ( *s != '' ) {
h = 127 * h + (unsigned char)*s;
++ s;
}
return h;
}

unsigned
hash( char const* s )
{
unsigned h = 0;
while ( *s != '' ) {
h = (h << 7) - h + (unsigned char)*s;
++ s;
}
return h;
}

在我测试的每台机器上,第一台至少和第二。令人惊讶的是,它有时更快(例如Sun Sparc)。当硬件不支持快速乘法时(以及当时大多数人都没有),编译器会转换乘法转换成移位和加法的适当组合。因为知道最终目标,有时它可以在比当您显式地编写shift和add/subs。

请注意,这大约是15年前的事了。希望编译器从那以后只会变得更好,所以你几乎可以指望编译器做了正确的事情,可能比你能做的更好。(此外,代码看起来如此C’ish的原因是因为它已经超过15年了。我现在显然会使用std::string和迭代器。)

除了这里的所有其他好答案之外,让我指出另一个不使用移位的原因,当你指的是除法或乘法时。我从来没有见过有人因为忘记乘法和加法的相对优先级而引入错误。当维护程序员忘记通过移位的"乘法"在逻辑上是乘法,而在语法上不是与乘法具有相同优先级的时,我看到了引入的错误。x * 2 + zx << 1 + z非常不同!

如果您正在处理数字,请使用像+ - * / %这样的算术运算符。如果您正在处理位数组,请使用像& ^ | >>这样的位旋转运算符。不要把它们混在一起;一个同时具有位处理和算术的表达式是一个等待发生的错误。

这取决于处理器和编译器。一些编译器已经通过这种方式优化了代码,而另一些则没有。因此,每次需要以这种方式优化代码时,都需要进行检查。

除非您迫切需要优化,否则我不会仅仅为了保存汇编指令或处理器周期而扰乱源代码。

使用say(i<<3)+(i&llt;<1)与10相乘实际上比直接使用i*10更快吗?

它可能在您的机器上,也可能不在您的计算机上——如果您关心,请根据您的实际使用情况进行测量。

案例研究——从486到酷睿i7

制定基准很难有意义,但我们可以看看一些事实。从…起http://www.penguin.cz/~literalkl/intel/s.html#SAL和http://www.penguin.cz/~literalkl/intel/i.html#IMUL我们了解了算术移位和乘法所需的x86时钟周期。假设我们坚持使用"486"(列出的最新版本)、32位寄存器和立即数,IMUL需要13-42个周期,IDIV需要44个周期。每个SAL取2,再加1,所以即使其中一些加在一起,表面上看起来也像是赢家。

这些天,与核心i7:

(来自http://software.intel.com/en-us/forums/showthread.php?t=61481)

整数加法的延迟为1个周期,整数乘法的延迟为3个周期。您可以在"英特尔®;64与IA-32体系结构优化参考手册"的附录C中找到延迟和吞吐量,该手册位于http://www.intel.com/products/processor/manuals/.

(来自一些英特尔简介)

使用SSE,Core i7可以同时发出加法和乘法指令,从而使每个时钟周期的峰值速率达到8次浮点运算(FLOP)

这让你知道事情已经发展了多远。甚至在90年代就被认真对待的优化琐事——比如比特移位与*——现在已经过时了。比特移位仍然更快,但对于非二次幂mul/div,当你完成所有移位并添加结果时,它再次变慢。然后,更多的指令意味着更多的缓存故障,流水线中更多的潜在问题,更多地使用临时寄存器可能意味着更多地从堆栈中保存和恢复寄存器内容。。。很快就变得太复杂了,无法明确量化所有影响,但它们主要是负面的。

源代码中的功能与实现

更普遍地说,你的问题被标记为C和C++。作为第三代语言,它们是专门为隐藏底层CPU指令集的细节而设计的。为了满足他们的语言标准,他们必须支持乘法和移位运算(以及许多其他运算),即使底层硬件不支持。在这种情况下,他们必须使用许多其他指令来合成所需的结果。同样,如果CPU没有浮点运算,也没有FPU,它们必须为浮点运算提供软件支持。现代CPU都支持*<<,所以这在理论上和历史上可能看起来都很荒谬,但重要的是,选择实现的自由是双向的:即使CPU有一条指令,在一般情况下实现源代码中请求的操作,编译器可以自由选择它喜欢的其他东西,因为它更适合编译器所面临的特定于情况。

示例(使用假设的汇编语言)

source           literal approach         optimised approach
#define N 0
int x;           .word x                xor registerA, registerA
x *= N;          move x -> registerA
move x -> registerB
A = B * immediate(0)
store registerA -> x
...............do something more with x...............

像exclusive或(xor)这样的指令与源代码没有关系,但用xor本身清除任何东西会清除所有位,因此它可以用来将某个东西设置为0。暗示内存地址的源代码可能不需要使用任何地址。

自从电脑问世以来,这种黑客手段就一直在使用。在3GL的早期,为了确保开发人员的理解,编译器输出必须满足现有的核心手工优化汇编语言开发社区的要求,即生成的代码不会更慢、更详细或更糟糕。编译器很快就采用了许多出色的优化——它们成为了一个比任何单个汇编语言程序员都更好的集中存储库,尽管他们总是有可能错过在特定情况下至关重要的特定优化——人类有时可以把它搞出来,寻找更好的东西,而编译器只是按照他们被告知的那样做,直到有人把这种经验反馈给他们。

因此,即使在某些特定的硬件上,移位和加法仍然更快,那么编译器编写器很可能已经准确地计算出了何时既安全又有益。

可维护性

如果你的硬件发生了变化,你可以重新编译,它会查看目标CPU并做出另一个最佳选择,而你不太可能想重新审视你的"优化"或列出哪些编译环境应该使用乘法,哪些应该改变。想想10多年前写的两位移位的"优化"的所有非威力,它们现在正在减缓它们在现代处理器上运行的代码。。。!

值得庆幸的是,当启用任何优化(即...main(...) { return (argc << 4) + (argc << 2) + argc; }->imull $21, 8(%ebp), %eax)时,像GCC这样的优秀编译器通常可以用直接乘法代替一系列的移位和算术运算,因此即使不修复代码,重新编译也可能有所帮助,但这并不能保证。

实现乘法或除法的奇怪的位移代码远不能表达你在概念上试图实现的目标,所以其他开发人员会对此感到困惑,而困惑的程序员更有可能引入错误或删除一些必要的东西,以恢复看似理智的状态。如果你只在非显而易见的事情确实有益的时候才做,然后把它们记录好(但无论如何都不要记录其他直观的事情),每个人都会更快乐。

一般解决方案与部分解决方案

如果你有一些额外的知识,比如你的int实际上只存储值xyz,那么你可能能够制定出一些适用于这些值的指令,并比编译器没有这种洞察力并且需要一个适用于所有int值的实现时更快地得到结果。例如,考虑您的问题:

乘法和除法可以使用位运算符来实现。。。

你举例说明乘法,但除法怎么样?

int x;
x >> 1;   // divide by 2?

根据C++标准5.8:

-3-E1>>E2的值是E1右移的E2位位置。如果E1具有无符号类型,或者如果E1具有有符号类型和非负值,则结果的值是E1的商除以量2的整数部分,该量是E2的幂。如果E1具有带符号类型和负值,则生成的值是实现定义的。

因此,当x为负时,您的位偏移有一个实现定义的结果:它在不同的机器上可能无法以相同的方式工作。但是,/的工作更具可预测性(它可能也不完全一致,因为不同的机器可能有不同的负数表示,因此即使有相同数量的比特组成表示,范围也不同。)

你可能会说"我不在乎……int正在存储员工的年龄,它永远不会是负面的"。如果你有这种特殊的见解,那么是的——除非你在代码中明确地这样做,否则你的>>安全优化可能会被编译器忽略。但是,这是有风险的,而且很少有用,因为大多数时候你没有这种洞察力,而其他处理同一代码的程序员不会知道你把赌注押在了对将要处理的数据的一些不寻常的期望上。。。对他们来说,看似完全安全的改变可能会因为你的"优化"而适得其反。

有什么输入不能用这种方式相乘或除法吗?

是。。。如上所述,当被比特移位"除"时,负数具有实现定义的行为。

刚刚在我的机器上尝试编译这个:

int a = ...;
int b = a * 10;

拆卸时产生输出:

MOV EAX,DWORD PTR SS:[ESP+1C] ; Move a into EAX
LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; Multiply by 5 without shift !
SHL EAX, 1 ; Multiply by 2 using shift

这个版本比纯移位和加法的手动优化代码更快。

你真的永远不知道编译器会想出什么,所以最好简单地写一个正常乘法,让他按照自己想要的方式优化,除非在非常精确的情况下,你知道编译器无法优化

移位通常比指令级的乘法快得多,但您很可能会浪费时间进行过早的优化。编译器可以在编译时很好地执行这些优化。自己动手会影响可读性,可能对性能没有影响。如果你已经分析过并发现这是一个瓶颈,那么做这样的事情可能才是值得的。

事实上,被称为"魔术分割"的分割技巧实际上可以产生巨大的回报。同样,您应该首先进行配置,看看是否需要。但是,如果你真的使用它,周围有一些有用的程序可以帮助你找出相同划分语义需要什么指令。以下是一个示例:http://www.masm32.com/board/index.php?topic=12421.0

我从MASM32上的OP线程中举了一个例子:

include ConstDiv.inc
...
mov eax,9999999
; divide eax by 100000
cdiv 100000
; edx = quotient

将生成:

mov eax,9999999
mov edx,0A7C5AC47h
add eax,1
.if !CARRY?
mul edx
.endif
shr edx,16

Shift和整数乘法指令在大多数现代CPU上具有类似的性能-整数乘法指令早在20世纪80年代就相对较慢,但通常情况下已不再如此。整数乘法指令可能具有更高的延迟,因此在某些情况下,移位可能更可取。对于可以让更多执行单元忙碌的情况也是如此(尽管这可以双向切换)。

不过,整数除法仍然相对较慢,因此使用移位而不是2的幂除法仍然是一种胜利,大多数编译器都会将其作为优化来实现然而,请注意,要使此优化有效,股息必须是无符号的,或者必须已知为正。对于负股息,转移和除法是不等价的

#include <stdio.h>
int main(void)
{
int i;
for (i = 5; i >= -5; --i)
{
printf("%d / 2 = %d, %d >> 1 = %dn", i, i / 2, i, i >> 1);
}
return 0;
}

输出:

5 / 2 = 2, 5 >> 1 = 2
4 / 2 = 2, 4 >> 1 = 2
3 / 2 = 1, 3 >> 1 = 1
2 / 2 = 1, 2 >> 1 = 1
1 / 2 = 0, 1 >> 1 = 0
0 / 2 = 0, 0 >> 1 = 0
-1 / 2 = 0, -1 >> 1 = -1
-2 / 2 = -1, -2 >> 1 = -1
-3 / 2 = -1, -3 >> 1 = -2
-4 / 2 = -2, -4 >> 1 = -2
-5 / 2 = -2, -5 >> 1 = -3

因此,如果您想帮助编译器,请确保被除数中的变量或表达式是显式无符号的。

它完全取决于目标设备、语言、目的等。

视频卡驱动程序中的像素运算?很有可能,是的!

.NET业务应用程序?完全没有理由去调查它。

对于移动设备的高性能游戏来说,它可能值得研究,但只有在执行了更容易的优化之后。

除非绝对需要,否则不要这样做,并且您的代码意图需要移位而不是乘法/除法。

在通常的一天里,你可能会节省很少的机器周期(或者很宽松,因为编译器更清楚优化什么),但成本不值得——你把时间花在小细节上,而不是实际工作上,维护代码变得更加困难,你的同事会诅咒你。

对于高负载计算,您可能需要这样做,在高负载计算中,每个保存的周期都意味着几分钟的运行时间。但是,您应该一次优化一个地方,每次都进行性能测试,看看是真的让它更快还是破坏了编译器的逻辑。

据我所知,在一些机器中,乘法可能需要多达16到32个机器周期。所以,根据机器类型的不同,位移运算符比乘法/除法更快。

然而,某些机器确实有数学处理器,其中包含乘法/除法的特殊指令。

我同意德鲁·霍尔的标记答案。答案可能需要一些额外的注释。

对于绝大多数软件开发人员来说,处理器和编译器不再与这个问题相关。我们大多数人都远远超出了8088和MS-DOS。它可能只适用于那些仍在为嵌入式处理器开发的人。。。

在我的软件公司,数学(add/sub/mul/div)应该用于所有数学。而在数据类型之间转换时应使用Shift,例如将ushort转换为字节(n>>8)和而不是n/256。

在有符号整数和右移与除法的情况下,它可以产生影响。对于负数,移位向负无穷大取整,而除法向零取整。当然,编译器会将除法更改为更便宜的除法,但通常会将其更改为与除法具有相同舍入行为的除法,因为它要么无法证明变量不会为负数,要么根本不在乎。因此,如果你能证明一个数字不会是负数,或者你不在乎它会以哪种方式取整,你就可以用一种更有可能产生影响的方式进行优化。

Python测试对相同的随机数执行相同的乘法1亿次。

>>> from timeit import timeit
>>> setup_str = 'import scipy; from scipy import random; scipy.random.seed(0)'
>>> N = 10*1000*1000
>>> timeit('x=random.randint(65536);', setup=setup_str, number=N)
1.894096851348877 # Time from generating the random #s and no opperati
>>> timeit('x=random.randint(65536); x*2', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); x << 1', setup=setup_str, number=N)
2.2616429328918457
>>> timeit('x=random.randint(65536); x*10', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); (x << 3) + (x<<1)', setup=setup_str, number=N)
2.9485139846801758
>>> timeit('x=random.randint(65536); x // 2', setup=setup_str, number=N)
2.490908145904541
>>> timeit('x=random.randint(65536); x / 2', setup=setup_str, number=N)
2.4757170677185059
>>> timeit('x=random.randint(65536); x >> 1', setup=setup_str, number=N)
2.2316000461578369

因此,在python中进行移位而不是乘/除2的幂,有一点改进(除法为~10%;乘法为~1%)。如果它不是二次方,那么很可能会出现相当大的放缓。

同样,这些#将根据您的处理器、编译器(或解释器——为了简单起见,在python中进行了更改)而改变。

和其他人一样,不要过早地进行优化。编写可读性很强的代码,如果速度不够快,则进行评测,然后尝试优化速度较慢的部分。记住,你的编译器在优化方面比你强得多。

编译器无法进行某些优化,因为它们只适用于减少的输入集。

下面是c++示例代码,它可以更快地进行64位的除法运算"乘倒数"。分子和分母都必须低于某个阈值。请注意,它必须被编译为使用64位指令才能比正常除法更快。

#include <stdio.h>
#include <chrono>
static const unsigned s_bc = 32;
static const unsigned long long s_p = 1ULL << s_bc;
static const unsigned long long s_hp = s_p / 2;
static unsigned long long s_f;
static unsigned long long s_fr;
static void fastDivInitialize(const unsigned d)
{
s_f = s_p / d;
s_fr = s_f * (s_p - (s_f * d));
}
static unsigned fastDiv(const unsigned n)
{
return (s_f * n + ((s_fr * n + s_hp) >> s_bc)) >> s_bc;
}
static bool fastDivCheck(const unsigned n, const unsigned d)
{
// 32 to 64 cycles latency on modern cpus
const unsigned expected = n / d;
// At least 10 cycles latency on modern cpus
const unsigned result = fastDiv(n);
if (result != expected)
{
printf("Failed for: %u/%u != %un", n, d, expected);
return false;
}
return true;
}
int main()
{
unsigned result = 0;
// Make sure to verify it works for your expected set of inputs
const unsigned MAX_N = 65535;
const unsigned MAX_D = 40000;
const double ONE_SECOND_COUNT = 1000000000.0;
auto t0 = std::chrono::steady_clock::now();
unsigned count = 0;
printf("Verifying...n");
for (unsigned d = 1; d <= MAX_D; ++d)
{
fastDivInitialize(d);
for (unsigned n = 0; n <= MAX_N; ++n)
{
count += !fastDivCheck(n, d);
}
}
auto t1 = std::chrono::steady_clock::now();
printf("Errors: %u / %u (%.4fs)n", count, MAX_D * (MAX_N + 1), (t1 - t0).count() / ONE_SECOND_COUNT);
t0 = t1;
for (unsigned d = 1; d <= MAX_D; ++d)
{
fastDivInitialize(d);
for (unsigned n = 0; n <= MAX_N; ++n)
{
result += fastDiv(n);
}
}
t1 = std::chrono::steady_clock::now();
printf("Fast division time: %.4fsn", (t1 - t0).count() / ONE_SECOND_COUNT);
t0 = t1;
count = 0;
for (unsigned d = 1; d <= MAX_D; ++d)
{
for (unsigned n = 0; n <= MAX_N; ++n)
{
result += n / d;
}
}
t1 = std::chrono::steady_clock::now();
printf("Normal division time: %.4fsn", (t1 - t0).count() / ONE_SECOND_COUNT);
getchar();
return result;
}

我认为,在一种情况下,如果你想乘或除2的幂,即使编译器将其转换为MUL/div,使用位移位运算符也不会出错,因为有些处理器无论如何都会对它们进行微码(实际上是宏),所以在这些情况下,你会得到改进,尤其是当移位大于1时。或者更明确地说,如果CPU没有位移运算符,那么它无论如何都将是MUL/DIV,但如果CPU有位移位运算符,则可以避免微码分支,这会减少一些指令。

我现在正在写一些需要大量加倍/减半操作的代码,因为它在一个密集的二叉树上工作,我怀疑还有一个操作可能比加法更优化——用加法左移(2乘的幂)。如果移位比要添加的位数宽,则可以用左移位和异或来替换,简单的例子是(i<<1)^1,它将一添加到加倍的值。这当然不适用于右移(二次幂除法),因为只有左(小端序)移位会用零填充间隙。

在我的代码中,这些乘/除2和2次幂运算的使用非常密集,而且由于公式已经很短了,所以可以消除的每条指令都是一个巨大的增益。如果处理器不支持这些位移运算符,则不会产生增益,但也不会产生损失。

此外,在我写的算法中,它们在视觉上代表了发生的运动,因此从这个意义上说,它们实际上更清晰。二叉树的左手边较大,右手边较小。除此之外,在我的代码中,奇数和偶数都有特殊的意义,树中所有左边的子项都是奇数,所有右边的子项和根都是偶数。在某些情况下,我还没有遇到,但可能,哦,实际上,我甚至没有想到这一点,x&与x%2相比,1可能是更优化的操作。x&偶数上的1将产生0,但奇数上的1会产生1。

如果我对x&3我知道4是我们数字的一个因子,x%7和8也是如此,依此类推。我知道这些情况可能效用有限,但很高兴知道你可以避免模运算,而是使用逐位逻辑运算,因为逐位运算几乎总是最快的,对编译器来说最不可能含糊不清。

我几乎是在发明密集二叉树领域,所以我预计人们可能不会理解这一评论的价值,因为人们很少只想对二次幂进行因子分解,或者只对二次方进行乘/除。

是否真的更快取决于实际使用的硬件和编译器

如果比较x+x、x*2和x<lt;1语法,那么在x86汇编中会得到相同的结果:https://godbolt.org/z/JLpp0j

push    rbp
mov     rbp, rsp
mov     DWORD PTR [rbp-4], edi
mov     eax, DWORD PTR [rbp-4]
add     eax, eax
pop     rbp
ret

因此,您可以将gcc视为smart,以便独立于您键入的内容来确定自己的最佳解决方案。

我也想看看我是否能打败众议院。对于任何数字乘以任何数字的乘积,这是一个更通用的逐位。我制作的宏比正常*乘法慢25%到两倍。正如其他人所说,如果它接近2的倍数或由2的几个倍数组成,你可能会赢。如由(X<<4)+(X&llt;<2)+(X<<1)+X组成的X*23将慢于由(X<&llt;6)+X构成的X*65。

#include <stdio.h>
#include <time.h>
#define MULTIPLYINTBYMINUS(X,Y) (-((X >> 30) & 1)&(Y<<30))+(-((X >> 29) & 1)&(Y<<29))+(-((X >> 28) & 1)&(Y<<28))+(-((X >> 27) & 1)&(Y<<27))+(-((X >> 26) & 1)&(Y<<26))+(-((X >> 25) & 1)&(Y<<25))+(-((X >> 24) & 1)&(Y<<24))+(-((X >> 23) & 1)&(Y<<23))+(-((X >> 22) & 1)&(Y<<22))+(-((X >> 21) & 1)&(Y<<21))+(-((X >> 20) & 1)&(Y<<20))+(-((X >> 19) & 1)&(Y<<19))+(-((X >> 18) & 1)&(Y<<18))+(-((X >> 17) & 1)&(Y<<17))+(-((X >> 16) & 1)&(Y<<16))+(-((X >> 15) & 1)&(Y<<15))+(-((X >> 14) & 1)&(Y<<14))+(-((X >> 13) & 1)&(Y<<13))+(-((X >> 12) & 1)&(Y<<12))+(-((X >> 11) & 1)&(Y<<11))+(-((X >> 10) & 1)&(Y<<10))+(-((X >> 9) & 1)&(Y<<9))+(-((X >> 8) & 1)&(Y<<8))+(-((X >> 7) & 1)&(Y<<7))+(-((X >> 6) & 1)&(Y<<6))+(-((X >> 5) & 1)&(Y<<5))+(-((X >> 4) & 1)&(Y<<4))+(-((X >> 3) & 1)&(Y<<3))+(-((X >> 2) & 1)&(Y<<2))+(-((X >> 1) & 1)&(Y<<1))+(-((X >> 0) & 1)&(Y<<0))
#define MULTIPLYINTBYSHIFT(X,Y) (((((X >> 30) & 1)<<31)>>31)&(Y<<30))+(((((X >> 29) & 1)<<31)>>31)&(Y<<29))+(((((X >> 28) & 1)<<31)>>31)&(Y<<28))+(((((X >> 27) & 1)<<31)>>31)&(Y<<27))+(((((X >> 26) & 1)<<31)>>31)&(Y<<26))+(((((X >> 25) & 1)<<31)>>31)&(Y<<25))+(((((X >> 24) & 1)<<31)>>31)&(Y<<24))+(((((X >> 23) & 1)<<31)>>31)&(Y<<23))+(((((X >> 22) & 1)<<31)>>31)&(Y<<22))+(((((X >> 21) & 1)<<31)>>31)&(Y<<21))+(((((X >> 20) & 1)<<31)>>31)&(Y<<20))+(((((X >> 19) & 1)<<31)>>31)&(Y<<19))+(((((X >> 18) & 1)<<31)>>31)&(Y<<18))+(((((X >> 17) & 1)<<31)>>31)&(Y<<17))+(((((X >> 16) & 1)<<31)>>31)&(Y<<16))+(((((X >> 15) & 1)<<31)>>31)&(Y<<15))+(((((X >> 14) & 1)<<31)>>31)&(Y<<14))+(((((X >> 13) & 1)<<31)>>31)&(Y<<13))+(((((X >> 12) & 1)<<31)>>31)&(Y<<12))+(((((X >> 11) & 1)<<31)>>31)&(Y<<11))+(((((X >> 10) & 1)<<31)>>31)&(Y<<10))+(((((X >> 9) & 1)<<31)>>31)&(Y<<9))+(((((X >> 8) & 1)<<31)>>31)&(Y<<8))+(((((X >> 7) & 1)<<31)>>31)&(Y<<7))+(((((X >> 6) & 1)<<31)>>31)&(Y<<6))+(((((X >> 5) & 1)<<31)>>31)&(Y<<5))+(((((X >> 4) & 1)<<31)>>31)&(Y<<4))+(((((X >> 3) & 1)<<31)>>31)&(Y<<3))+(((((X >> 2) & 1)<<31)>>31)&(Y<<2))+(((((X >> 1) & 1)<<31)>>31)&(Y<<1))+(((((X >> 0) & 1)<<31)>>31)&(Y<<0))
int main()
{
int randomnumber=23;
int randomnumber2=23;
int checknum=23;
clock_t start, diff;
srand(time(0));
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum=MULTIPLYINTBYMINUS(randomnumber,randomnumber2);
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
int msec = diff * 1000 / CLOCKS_PER_SEC;
printf("MULTIPLYINTBYMINUS Time %d milliseconds", msec);
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum=MULTIPLYINTBYSHIFT(randomnumber,randomnumber2);
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
msec = diff * 1000 / CLOCKS_PER_SEC;
printf("MULTIPLYINTBYSHIFT Time %d milliseconds", msec);
start = clock();
for(int i=0;i<1000000;i++)
{
randomnumber = rand() % 10000;
randomnumber2 = rand() % 10000;
checknum= randomnumber*randomnumber2;
if (checknum!=randomnumber*randomnumber2)
{
printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
}
}
diff = clock() - start;
msec = diff * 1000 / CLOCKS_PER_SEC;
printf("normal * Time %d milliseconds", msec);
return 0;
}