以下哪种技术是将整数除以2的最佳选择,为什么?
技巧1:
技术2:
这里x是一个整数。
-
类似的问题(但不是欺骗):stackoverflow.com/questions/1949271/
-
如果您真的想再次将结果分配给x,则这两种方式都不合适:它应该是x >>= 1或x /= 2,具体取决于您打算用操作表达的内容。不是因为它更快(任何现代编译器都会将所有等效变体编译成相同的快速装配),但因为它不那么令人困惑。
-
我不同意左撇子。 - 但我认为值得注意的是,在许多编程语言中都存在一种称为算术移位的操作,它将符号位保持在原位,因此可以按预期使用符号值。语法可能类似于x = x >>> 1。另请注意,根据平台和编译器,使用移位手动优化分割和乘法可能是非常合理的。 - 考虑微控制器,例如,没有直接ALU支持乘法。
-
我更喜欢x /= 2,因为x >>= 1看起来太像monadic bind;)
-
@HannoBinder以哪种方式不同意?
-
@leftaroundabout - 我认为编写x = x / 2而不是x /= 2更具可读性。主观偏好也许:)
-
@HannoBinder:肯定是主观的,特别是很多习惯。 IMO,在所有算术运算符都具有?=组合的语言中,只要有可能就应该使用它们。它消除了噪声并强调了x被修改的事实,而一般的=运算符则建议它采用一个独立于旧值的全新值。 - 总是避免使用组合运算符(这样它的可读性,以便只知道数学运算符的人)也可能有它的意义,但是你也需要放弃非常有用的++,--,+= 。
-
重复:c-left-shift-or-add-to-self-speed,by-power-of-value-in-shift,is-using-shift-operators-actual-faster-faster
-
另一个类似的问题:stackoverflow.com/questions/53757/
-
换位仅适用于无符号整数。和其他类型的数字位表示是完全不同的。
-
@leftaroundabout当使用支持运算符重载的C ++或其他语言时,它实际上可以通过直接更新已分配对象的内部状态来减少临时副本(因为重载的运算符用法最终转换为函数调用,不能保证编译器将能够优化掉最终可能最终创建的所有临时数据,尽管其中一些可能是由于在现代编译器中移动语义/复制省略)。
使用最能描述您要执行的操作的操作。
-
如果您将数字视为位序列,请使用bitshift。
-
如果您将其视为数值,请使用除法。
请注意,它们并不完全等效。它们可以为负整数提供不同的结果。例如:
1 2
| -5 / 2 = -2
-5 >> 1 = -3 |
(ideone)
-
最初的问题对于"最佳"一词也含糊不清。在速度,可读性,欺骗学生的考试问题等方面"最好"......在没有"最佳"含义的解释的情况下,这似乎是最正确的答案。
-
在C ++ 03中,两者都是针对负数定义的实现,并且可能给出相同的结果。在C ++ 11中,除法是针对负数定义的,但转换仍然是实现定义的。
-
虽然/ C的定义是在早期C标准中定义的实现(如果对负数进行向上或向下舍入)。它必须始终与%(模/余数运算符)一致。
-
ALL的负数不同的结果。
-
@JamesKanze -"实现定义"是指CPU的架构吗?哪些变量/常数会影响输出?
-
"实现定义"意味着编译器的实现者必须在几个实现选择中进行选择,通常具有实质性约束。这里,一个约束是%和/运算符必须对正负操作数都一致,因此无论a和b的符号如何,(a/b)*b+(a%b)==a都为真。通常,作者会做出选择,从CPU中获得最佳性能。
-
你需要考虑整数是一个补充。即使最前面的位是有效的有符号位,其他位也不是。例如,-1表示为11111111
-
对于C / C ++,Java,bc,expr,数学运算正确。但是对于Ruby,Python和Tcl,-5 / 2 = -3,而 - (5/2)= -2,反直觉但值得注意。见bugs.ruby-lang.org/issues/3289
-
@richard C ++ 11明确定义了负数的模运算符。
-
对于仅查看已接受的解决方案的后来的观看者来说,最好至少包含一个关于编译器如何最有可能从两者生成相同代码的简短句子。
-
@JimThio首先,负数的表示取决于机器。第二,到目前为止,最广泛的代表是两个补码,而不是补码。在32位二进制补码中,-1是0xFFFFFFFF;右移,实现定义是否导致0x7FFFFFFF(2147483647)或0xFFFFFFFF( - 1)。
-
@RBerteig此外,"实现定义"意味着需要实现来记录它的作用。
-
@JamesKanze的课程文件预计将作为交易的一部分。该文档的质量可能是编译器标准符合性质的良好指标。
-
@RBerteig每个程序都有文档。在"实现定义"的情况下,标准表示如果在这些情况下没有记录其行为,则实现是不符合的。 (根据我的经验,大多数编译器在这方面是不符合的。否则他们的文档特别隐藏。)
-
虽然我认为这是一个很好的答案,但我很惊讶这个(以及其他答案,包括我自己的答案)已经收到了这么多的赞成。它们并不是特别深刻。
-
在C标准中,功能可以是:由标准定义,实现定义或未定义。前两个是由标准或单个实现定义的,第三个undefined不必定义,它可以每次都不同。
-
所以每个说"编译器都会将它转换为移位"的人都是错的,对吧?除非编译器可以保证您正在处理非负整数(无论是常量还是无符号整数),它都不能将其更改为一个班次
-
@RBerteig:在许多处理器上,使用truncate-to-zero将未知符号的值除以未知数量将是最快的,但除以许多已知量(包括2的幂,但在许多情况下还有相当多的其他值) )使用truncate-negative-negative-infinity会更快。
-
@ryenus:虽然-5 / 2 = -3与C派生语言不同,但我想不出任何我想要计算被截断为零的商的程序;我所见过的唯一程序,如果编译器向负无穷大舍入的是那些在除法之前调整负值的编程器,那么编译器的截断向零将产生截断向负无穷大的结果本来没有调整。你能提供反例吗?
第一个看起来像分裂吗?不。如果要分割,请使用x / 2。如果可能的话,编译器可以对其进行优化以使用位移(称为强度降低),如果您自己进行,则会使其成为无用的微优化。
-
许多编译器不会将2的幂除以分段。对于有符号整数,这将是一个不正确的优化。您应该尝试查看编译器的汇编输出并亲自查看。
-
IIRC我用它来在CUDA上更快地进行并行缩减(避免整数div)。然而,这是一年多以前,我想知道现在CUDA编译器有多聪明。
-
@exDM69:许多编译器甚至会对有符号整数执行此操作,并根据签名调整它们。使用这些东西的好工具是:tinyurl.com/6uww253
-
@exDM69:那是相关的,怎么样?我说"如果可能",而不是"永远"。如果优化不正确,那么手动执行操作并不能使其正确(另外,如上所述,GCC足够聪明,可以找出对有符号整数的正确替换)。
-
看看WikiPedia页面,这显然是有争议的,但我不会称之为强度降低。强度降低是指在循环中通过向循环中的先前值添加,从例如乘法到加法减少。这更像是一种窥视孔优化,编译器可以非常可靠地完成。
要继续:有很多理由支持使用x = x / 2;以下是一些:
-
它更清楚地表达了你的意图(假设你没有处理比特错误的寄存器位或其他东西)
-
无论如何,编译器会将其减少为移位操作
-
即使编译器没有减少它并选择比移位更慢的操作,这最终会以可测量的方式影响程序性能的可能性本身也很小(如果它确实影响了它,那么你有一个实际的使用班次的原因)
-
如果除法将成为更大表达式的一部分,那么如果使用除法运算符,则更有可能获得优先权:
1 2
| x = x / 2 + 5;
x = x >> 1 + 5; // not the same as above |
-
签名算术可能比上面提到的优先级问题更复杂
-
重申 - 无论如何编译器已经为你做了这个。实际上,它会将除以常数转换为各种数字的一系列移位,加法和乘法,而不仅仅是2的幂。请参阅此问题以获取有关此内容的更多信息的链接。
简而言之,当你真正意味着乘法或除法时,你不会通过编码转移来购买任何东西,除非可能增加引入错误的可能性。这是一辈子的事情,因为编译器不够智能,无法在适当的时候优化这种转变。
-
还有一点值得补充的是,虽然有优先规则,但使用括号并没有错。在修改一些生产代码时,我实际上看到了a/b/c*d形式的东西(其中a..d表示数字变量),而不是更易读的(a*d)/(b*c)。
-
性能和优化取决于编译器和目标。例如,我为微控制器做了一些工作,除非你购买商业编译器,否则禁止高于-O0的任何东西,所以编译器肯定不会转换为位移。此外,位移需要一个周期,并且在此目标上除以18个周期,并且由于微控制器的时钟速度非常低,这可能确实是一个明显的性能损失(但它取决于您的代码 - 您应该使用/直到分析告诉您这是一个问题!)
-
@JackManey,如果a*d或b*c有可能产生溢出,那么可读性较低的形式就不相同而且具有明显的优势。附:我同意括号是你最好的朋友。
-
@MarkRansom - 一个公平的观点(即使我在R代码中遇到a/b/c*d - 在溢出意味着数据严重错误的情况下 - 而不是在C的性能关键块中码)。
-
代码x=x/2;仅比x>>=1"更清晰",如果x永远不会是奇数负数,或者一个人不关心逐个错误。否则x=x/2;和x>>=1;具有不同的含义。如果需要的是由x>>=1计算的值,我会认为比x = (x & ~1)/2或x = (x < 0) ? (x-1)/2 : x/2更清晰,或者我可以想到使用除以2的任何其他公式。同样,如果需要x/=2计算的值,则比((x + ((unsigned)x>>31)>>1)更清晰。
-
@Dan:为非优化编译器编写代码非常简单。您将不得不以比尝试优化asm更多的方式编写不易读取的代码。此外,即使在-O0,gcc和icc实现了带有shift + adjust-for-sign-bit的2次有符号除法。 (godbolt链接)。 clang -O0实际上确实使用了div。现代x86 CPU具有类似于div与shift的延迟和吞吐量比率,所以是的,对于编译器来说,避免div是一个大问题。它可能不像分支错误预测那么糟糕,但也许类似于在L2中遇到的L1未命中
-
@PeterCordes我同意。这就是我说The performance and optimizations depend on the compiler and target和you should definitely use / until profiling tells you its a problem!的原因。我的观点是,存在极端情况(例如微控制器,在我的情况下,它是PIC24,至少在我撰写评论时,它没有优化/ -O0)差异,这取决于你是否在这些角落的情况下工作。
Which one is the best option and why for dividing the integer number by 2?
取决于你最好的意思。
如果你想让你的同事讨厌你,或者让你的代码难以阅读,我肯定会选择第一个选项。
如果要将数字除以2,请使用第二个数字。
两者不相等,如果数字为负数或在较大的表达式内,它们的行为不相同 - bitshift的优先级低于+或-,除法具有更高的优先级。
您应该编写代码来表达其意图。如果您关心性能,请不要担心,优化器在这些微优化方面做得很好。
只需使用除(/),假设它更清晰。编译器将相应地进行优化。
-
编译器应相应地进行优化。
-
如果编译器没有相应地进行优化,则应使用更好的编译器。
-
@DavidStone:在什么处理器上,编译器可以优化除了1以外的任何常数对可能为负的有符号整数的划分,以便与移位一样高效?
-
@supercat:这是一个好点。你当然可以将值存储在一个无符号整数中(我认为它的声誉比它与有符号/无符号不匹配警告结合时的声誉要差得多),并且大多数编译器也有一种方法可以告诉他们在优化时假设某些事情是真的。 。我更喜欢在兼容性宏中包装它并且具有ASSUME(x >= 0); x /= 2; over x >>= 1;之类的东西,但这仍然是一个重要的点。
我赞同你应该支持x / 2的其他答案,因为它的意图更清晰,编译器应该为你优化它。
但是,优先选择x / 2而不是x >> 1的另一个原因是>>的行为是依赖于实现的,如果x是带符号的int并且是负的。
从6.5.7节,ISO C99标准的第5章:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has
an unsigned type or if E1 has a signed type and a nonnegative value,
the value of the result is the integral part of the quotient of E1 /
2E2. If E1 has a signed type and a negative value, the resulting value
is implementation-defined.
-
值得注意的是,许多实现在负数上为x>>scalepower定义的行为正是为了屏幕渲染等目的而将值除以2的幂时所需的行为,而使用x/scalefactor将是错误的,除非一个将修正应用于负值。
x / 2更清晰,x >> 1的速度并不快(根据微基准测试,Java JVM快30%左右)。正如其他人所指出的那样,对于负数,舍入略有不同,所以当你想要处理负数时你必须考虑这个。一些编译器可能会自动将x / 2转换为x >> 1,如果他们知道数字不能为负数(甚至认为我无法验证这一点)。
即使x / 2也可能不使用(慢)除法CPU指令,因为某些快捷键是可能的,但它仍然比x >> 1慢。
(这是一个C / C ++问题,其他编程语言有更多的运算符。对于Java,还有无符号右移x >>> 1,这又是不同的。它允许正确计算两个值的平均值(平均值),这样(a + b) >>> 1即使对于非常大的a和b值也会返回平均值。如果数组索引可能变得非常大,这对于二进制搜索是必需的。许多版本的二进制文件中存在一个错误搜索,因为他们使用(a + b) / 2来计算平均值。这不能正常工作。正确的解决方案是使用(a + b) >>> 1代替。)
-
在x可能为负的情况下,编译器无法将x/2转换为x>>1。如果想要的是x>>1将计算的值,那么几乎肯定会比涉及计算相同值的x/2的任何表达式更快。
-
你是对的。如果编译器知道值不是负数,则它只能将x/2转换为x>>1。我会尝试更新我的答案。
-
编译器仍然通过将x/2转换为(x + (x<0?1:0)) >> 1(其中>>是算术右移,符号位移位)来避免div指令。这需要4条指令:复制值,shr(只获取reg中的符号位),add,sar。 goo.gl/4F8Ms4
-
问题标记为C和C ++。
克努特说:
Premature optimization is the root of all evil.
所以我建议使用x /= 2;
这样代码很容易理解,而且我认为以这种形式优化这个操作,并不意味着处理器有很大的不同。
-
如果想要整数来维持公理(适用于自然数和实数)(n + d)/ d =(n / d)+,那么你会认为将数字缩减2的幂的首选方法是什么? 1?缩放图形时违反惯例会导致结果中出现明显的"接缝"。如果想要一个统一且几乎对称于零的东西,(n+8)>>4可以很好地工作。您是否可以提供任何明确或有效的方法而不使用签名的右移?
看看编译器输出,以帮助您决定。我在x86-64上运行了这个测试
gcc(GCC)4.2.1 20070719 [FreeBSD]
另请参阅godbolt在线编译器输出。
你看到的是编译器在两种情况下都使用sarl(算术右移)指令,因此它确实识别两个表达式之间的相似性。如果使用除法,编译器还需要调整负数。为此,它将符号位向下移动到最低位,并将其添加到结果中。与分数相比,这可以在转移负数时解决一个一个问题。
由于除法情况确实有2个移位,而显式移位情况只做了一个,我们现在可以解释其他答案所测量的一些性能差异。
带汇编输出的C代码:
对于鸿沟,您的输入将是
1 2 3
| int div2signed(int a) {
return a / 2;
} |
这就编译成了
1 2 3 4 5
| movl %edi, %eax
shrl $31, %eax
addl %edi, %eax
sarl %eax
ret |
同样的转变
1 2 3
| int shr2signed(int a) {
return a >> 1;
} |
输出:
1 2 3
| sarl %edi
movl %edi, %eax
ret |
-
根据正在做的事情,它可以修复一个一个错误,或者它可能导致一个一个错误(与实际需要的错误相比),这将需要使用更多代码来修复它。如果一个人想要的是一个平庸的结果,那么右移比我所知道的任何替代方案更快更容易。
-
如果你需要一个楼层,你不太可能描述你想要的"除以2"
-
自然数和实数的划分维持了(n + d)/ d =(n / d)+1的公理。实数的划分也支持(-n)/ d = - (n / d),这是一个对自然数无意义的公理。不可能有一个除法运算符,它在整数上是闭合的,并且支持两个公理。在我看来,说第一个公理应该适用于所有数字而第二个只适用于实数似乎比说第一个应该保持整数或实数而不是整数更自然。此外,我很好奇第二个公理在什么情况下实际上是有用的。
-
满足第一公理的整数除法方法将数字线划分为大小d的区域。这种分区对于许多目的是有用的。即使人们宁愿将断点放在0到-1之间的某个位置,添加偏移量也会移动它。满足第二公理的整数除法将数字线划分为大部分尺寸d但其中一个尺寸2*d-1的区域。不完全是"平等"的分歧。你能提供关于奇怪分区实际有用的建议吗?
-
shr2signed的编译器输出是错误的。 x86上的gcc选择实现带有算术移位(sar)的带符号整数的>>。 goo.gl/KRgIkb。这个邮件列表帖子(gcc.gnu.org/ml/gcc/2000-04/msg00152.html)证实gcc历史上使用了有符号整数的算术移位,因此FreeBSD gcc 4.2.1使用无符号移位的可能性极小。我更新了你的帖子以解决这个问题和早期的段落,说两者都使用了shr,当它们实际上是SAR时它们都使用了。 SHR是如何为/情况提取符号位的。还包括一个godbolt链接。
只是一个补充说明 -
在某些基于VM的语言中,x * = 0.5通常会更快 - 特别是actionscript,因为不必检查变量除以0。
-
好吗? jsperf.com/multiplication-vs-division
-
2不是变量。
-
@minitech:这是一个糟糕的测试。测试中的所有代码都是不变的。在代码甚至是JIT之前,它将消除所有常量。
-
@ M28:我非常确定jsPerf的内部(即eval)每次都会重新发生。无论如何,这是一个非常糟糕的测试,因为它是一个非常愚蠢的优化。
使用x = x / 2; OR x /= 2;因为将来新程序员可能会使用它。因此,他更容易找到代码行中发生的事情。每个人都可能不知道这种优化。
就CPU而言,位移操作比除法操作更快。
但是,编译器知道这一点并将在适当的范围内进行适当优化,
因此,您可以通过最有意义的方式进行编码,并在知道您的代码时轻松休息
有效地运作。但请记住,unsigned int可以(在某些情况下)优于int,原因如前所述。
如果您不需要签名算术,则不要包含符号位。
我告诉编程比赛的目的。通常它们具有非常大的输入,其中除以2发生多次并且已知输入为正或负。
x >> 1将优于x / 2。我通过运行一个程序来检查ideone.com,该程序进行了超过10 ^ 10除以2的操作。 x / 2花费了近5.5秒而x >> 1花费了近2.6秒的同一节目。
-
对于无符号值,编译器应将x/2优化为x>>1。对于有符号值,几乎所有实现都定义x>>1具有等于x/2的含义,但是当x为正时可以更快地计算,并且当x为负时有效地与x/2不同。
我想说有几件事需要考虑。
Bitshift应该更快,因为没有特别的计算
需要转移位,但正如所指出的,有
负数的潜在问题。如果你有保证
正数,正在寻找速度然后我会建议
位位移。
除法运算符对人类来说非常容易阅读。
因此,如果您正在寻找代码可读性,您可以使用它。注意
编译器优化领域已经走过了漫长的道路,因此使代码变得容易
阅读和理解是一种很好的做法。
根据底层硬件,
操作可能有不同的速度。 Amdal的法律是制定
常见的情况很快。所以你可能有可以执行的硬件
不同的操作比其他人更快。例如,乘以
0.5可能比除以2更快。(如果你想强制执行整数除法,你可能需要占用乘法的最低点)。
如果你是在追求纯粹的性能,我建议你创建一些可以进行数百万次操作的测试。多次尝试执行(您的样本大小),以确定哪个在统计上最适合您的OS /硬件/编译器/代码。
-
"Bitshift应该更快"。编译器将优化分区为位移
-
我希望他们会,但除非你有权访问编译器的源代码,否则你无法确定:)
-
如果一个人的实现以最常见的方式处理它,并且想要处理负数的方式与>>所做的匹配并且与/不匹配,我也会推荐bitshift。
x = x / 2;是适合使用的代码..但操作取决于您自己的程序,您希望如何生成输出。
让你的意图更清晰...例如,如果你想分裂,使用x / 2,让编译器优化它以移动运算符(或其他任何东西)。
今天的处理器不会让这些优化对程序的性能产生任何影响。
答案取决于您所处的环境。
-
如果您正在使用8位微控制器或任何没有硬件支持进行乘法运算的任何东西,那么位移是预期的并且很常见,而编译器几乎肯定会将x /= 2转换为x >>= 1,分区符号的存在将会提高在这种环境中比使用轮班实现分裂更多的眉毛。
-
如果您正在一个性能关键的环境或代码段工作,或者您的代码可以在编译器优化关闭的情况下进行编译,x >>= 1带有解释其推理的注释可能最好只是为了明确目的。
-
如果您不符合上述条件之一,只需使用x /= 2即可使代码更具可读性。更好的是保存下一个程序员,他们碰巧看你的代码,你的班次操作10秒加倍,而不必毫无疑问地证明你知道转换是更有效的没有编译器优化。
所有这些都假设无符号整数。简单的转变可能不是你想签名的。此外,DanielH提出了一个关于将x *= 0.5用于某些语言(例如ActionScript)的好处。
mod 2,test for = 1. dunno c中的语法。但这可能是最快的。
通常右移分为:
1
| q = i >> n; is the same as: q = i / 2**n; |
这有时用于以清晰为代价加速程序。我认为你不应该这样做。编译器足够智能,可以自动执行加速。这意味着投入班次不会以牺牲清晰度为代价。
从Practical C ++ Programming看一下这个页面。
-
如果想要计算例如(x+128)>>8会计算x的值不接近最大值,如何在没有移位的情况下简洁地做到这一点?请注意(x+128)/256不起作用。你知道任何好的表达吗?
显然,如果你是为下一个阅读它的人编写代码,那么请选择"x / 2"的清晰度。
但是,如果速度是您的目标,请尝试两种方式和结果。 几个月前,我研究了一个位图卷积例程,它涉及逐步遍历整数数组并将每个元素除以2.我做了各种各样的事情来优化它,包括用"x >> 1"代替"x"的旧技巧/ 2"。
当我实际上两种方式时,我惊讶地发现x / 2比x >> 1更快
这是使用Microsoft VS2008 C ++打开默认优化。
在性能方面。 CPU的移位操作比分割操作码快得多。
所以除以2或乘以2等都可以从换班操作中受益。
至于外观和感觉。作为工程师,我们什么时候变得如此依赖化妆品,即使是漂亮女士也不会使用! :)
X / Y是正确的...和">>"移位运算符..如果我们想要两个除以整数我们可以使用(/)被除数运算符。 shift运算符用于移位位..
X = X / 2;
X / = 2;我们可以像这样使用..
虽然x >> 1比x / 2快,但在处理负值时正确使用>>会稍微复杂一些。 它需要类似于以下内容:
1 2 3 4 5 6
| // Extension Method
public static class Global {
public static int ShiftDivBy2(this int x) {
return (x < 0 ? x + 1 : x) >> 1;
}
} |