将整数除以2哪个更好的选择?


406

以下哪种技术是将整数除以2的最佳选择,为什么?

技术1:

x = x >> 1;

技术2:

x = x / 2;

x是整数。


75
如果您确实要再次将结果分配给x,则这两种方法都不适合:应为x >>= 1x /= 2,这取决于您要在操作中表达的内容。不是因为它更快(任何现代编译器都会将所有等效变体编译为相同,快速的汇编),而是因为它不那么令人困惑。
左转

33
我不同意左转。-但我认为值得注意的是,在许多编程语言中都有一种称为算术移位的操作,可将符号位保持在适当的位置,因此可以按预期的方式处理有符号的值。语法可能像x = x >>> 1。还要注意,根据平台和编译器的不同,使用移位手动优化除法和乘法可能是非常合理的。-考虑微控制器,例如,没有直接的ALU乘法支持。
JimmyB 2012年

36
我更喜欢,x /= 2因为x >>= 1看起来太像单子绑定了;)
fredoverflow 2012年

19
@leftaroundabout-我认为编写x = x / 2而不是更具可读性x /= 2。主观上的偏爱可能是:)
JimmyB'5

8
@HannoBinder:当然是主观的,尤其是很多习惯。IMO,在所有算术运算符都有⬜=组合的语言中,应尽可能使用它们。它消除了对这一事实的噪音和着重x进行修改,而一般=操作,而表明,它发生在一个完全新的价值不依赖于旧的。-始终避免联合运营商(以便它的可读性,以便别人谁只知道数学运算符)可能有它的点为好,但随后你需要放弃是非常有用的++--+=,太。
大约

Answers:


847

使用最能描述您要执行的操作的操作。

  • 如果将数字视为位序列,请使用位移。
  • 如果将其视为数值,请使用除法。

请注意,它们并不完全等效。对于负整数,它们可以给出不同的结果。例如:

-5 / 2  = -2
-5 >> 1 = -3

(ideone)


20
最初的问题对于“最佳”一词也含糊不清。在速度,可读性,欺骗学生的考试问题等方面,“最佳”。在没有解释“最佳”含义的情况下,这似乎是最正确的答案。

47
在C ++ 03中,两者都是为负数定义的实现,并且可能给出相同的结果。在C ++ 11中,为负数很好地定义了除法,但移位仍由实现定义。
詹姆斯·坎泽

2
/的定义是在早期C标准中定义的实现(是否对负数进行四舍五入)。它必须始终与%(模/余数运算符)一致。
ctrl-alt-delor 2012年

7
“定义的实现”意味着编译器的实现者必须在几个实现选择中进行选择,通常会有很大的限制。在此,一个约束条件是%and /运算符对于正数和负数操作数都必须是一致的,因此(a/b)*b+(a%b)==a无论aand 的符号是什么,这都是正确的b。通常,作者将做出一些选择,以使CPU发挥出最佳性能。
RBerteig

6
所以每个说“编译器无论如何都会将其转换为班次”的人都错了,对吗?除非编译器可以保证您正在处理的是非负整数(它是一个常数还是一个无符号的int),否则它不能将其更改为shift
Kip

225

第一个看起来像分裂吗?否。如果要划分,请使用x / 2。编译器可以对其进行优化以在可能的情况下使用位移(这称为强度降低),如果您自己进行操作,则它将成为无用的微优化。


15
许多编译器不会将除以2的幂转换为位移。这将是对有符号整数的错误优化。您应该尝试查看编译器的程序集输出并亲自查看。
exDM69 2012年

1
IIRC我使用它来使CUDA上的并行缩减更快(避免使用整数div)。但是,这是一年多以前的事,我不知道当今的CUDA编译器有多聪明。
尼尔斯2012年

9
@ exDM69:即使是有符号整数,许多编译器也会这样做,只是根据有符号性对其进行调整。一个玩这些东西的好工具是:tinyurl.com/6uww253
PlasmaHH 2012年

19
@ exDM69:这很相关,如何?我说“如果可能”,而不是“总是”。如果优化不正确,则手动进行优化并不能使其正确(此外,如上所述,GCC足够聪明,可以找到有符号整数的适当替换项)。
Cat Plus Plus

4
查看WikiPedia页面,这显然是有争议的,但是我不会称其为强度降低。强度降低是指在循环中,您通过在循环中添加以前的值来将乘数减少为加法。这更多是窥视孔优化,编译器可以相当可靠地完成此操作。
SomeCallMeTim 2012年

189

值得一提的是:有很多理由支持使用x = x / 2; 这里有一些:

  • 它可以更清楚地表达您的意图(假设您不处理位扭曲寄存器位或其他东西)

  • 编译器无论如何都会将其减少为移位操作

  • 即使编译器没有减少它并选择了一个比移位要慢的操作,但这种情况最终以可测量的方式影响程序性能的可能性本身也就消失了(并且如果确实可测量地影响了它,那么您就可以使用班次的原因)

  • 如果除法将成为较大表达式的一部分,则使用除法运算符更有可能获得优先权:

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
  • 有符号算术可能使事情比上述优先级问题更为复杂

  • 重申一下-编译器已经可以为您完成此操作。实际上,它将除以常数转换为一系列移位,加法和乘除各种数字,而不仅仅是乘以2的幂。有关更多信息的链接,请参见此问题

简而言之,当您真正要乘或除时,只要编码一个移位就不会买到任何东西,只是可能会引入一个错误。因为编译器不够聪明,无法在适当的时候优化这种情况,所以这已经是一辈子了。


5
值得补充的是,尽管存在优先级规则,但使用括号并没有错。在修改一些生产代码时,我实际上看到的是某种形式的形式a/b/c*d(其中a..d表示为数字变量),而不是更具可读性的形式(a*d)/(b*c)

1
性能和优化取决于编译器和目标。例如,我为微控制器做一些工作,除非您购买了商用编译器,否则任何高于-O0的功能都将被禁用,因此该编译器绝对不会变成移位。此外,在此目标上,移位需要一个周期,而除法则需要18个周期,并且由于微控制器的时钟速度非常低,因此,这确实可能对性能造成明显的影响(但这取决于您的代码-您必须使用/,直到性能分析告诉您这是一个问题!)

4
@JackManey,如果有可能a*db*c将产生溢出,则可读性较低的形式不是等效的,并且具有明显的优势。附言:我同意括号是您最好的朋友。
Mark Ransom 2012年

@MarkRansom-一个公平的观点(即使我遇到a/b/c*d了R代码-在溢出的上下文中,这意味着数据有严重问题-而不是在性能关键的C代码块中)。

该代码x=x/2;是不是只“更清晰” x>>=1,如果x将永远是一个奇数负数或一个不关心的off-by-一个错误。否则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)
2013年

62

哪一个是最佳选择,为什么要将整数除以2?

最好取决于您的意思。

如果您希望您的同事讨厌您,或者使您的代码难以阅读,那么我绝对会选择第一种方法。

如果要将数字除以2,请选择第二个。

两者不相等,如果数字为负数或在较大的表达式内,则它们的行为不相同-移位的优先级低于+-,除的优先级更高。

您应该编写代码来表达其意图。如果您关心性能,请不用担心,优化器在这类微优化方面做得很好。


58

只需使用除法(/),假设它会更清晰。编译器将进行相应的优化。


34
编译器进行相应的优化。
Noctis Skytower 2012年

12
如果编译器没有进行相应的优化,则应使用更好的编译器。
大卫·斯通

3
@DavidStone:在哪些处理器,编译器可以优化除1以外的任何常量的可能为负的有符号整数的除法,以使其与移位一样有效?
2013年

1
@supercat:很好。您当然可以将值存储在无符号整数中(与带符号/无符号不匹配警告结合使用时,我认为它的信誉比它们差得多),并且大多数编译器还具有一种告诉它们在优化时假设某些情况正确的方法。我更喜欢将其包装在兼容性宏中,并具有类似ASSUME(x >= 0); x /= 2;over的东西x >>= 1;,但这仍然是重要的一点。
大卫·斯通

39

我同意您应该赞成的其他答案,x / 2因为它的意图更加明确,编译器应该为您优化它。

然而,另一个原因宁愿x / 2x >> 1是的行为>>是实现相关的,如果x是签名int和为负。

从6.5.7节开始,ISO C99标准的项目符号5:

结果E1 >> E2E1右移位E2位置。如果E1具有无符号类型或E1具有符号类型和非负值,则结果的值是E1/ 2 的商的整数部分E2。如果E1具有带符号的类型和负值,则结果值是实现定义的。


3
值得一提的是,许多实现针对x>>scalepower负数定义的行为恰恰是将值除以2的幂以实现屏幕渲染之类的目的所需要的行为,而x/scalefactor除非将校正应用于负值,否则使用是错误的。
2013年

32

x / 2更加清晰,并且运行起来x >> 1并不快(根据微基准测试,对于Java JVM而言,速度要快30%)。正如其他人指出的那样,对于负数,四舍五入略有不同,因此在处理负数时必须考虑这一点。如果某些编译器知道数字不能为负数,它们可能会自动转换x / 2x >> 1(即使以为我无法验证)。

甚至x / 2可能不使用(慢速)除法CPU指令,因为可以使用某些快捷方式,但是它仍然比慢x >> 1

(这是一个C / C ++问题,其他编程语言具有更多的运算符。对于Java,也存在无符号右移x >>> 1,这又是不同的。它允许正确计算两个值的均值(平均值),这样就(a + b) >>> 1可以即使aand的非常大的值也返回平均值b。例如,如果数组索引可以变得非常大,则对于二进制搜索是必需的。在许多版本的二进制搜索中存在一个错误,因为它们用于(a + b) / 2计算平均值。无法正常工作。请改用正确的解决方案(a + b) >>> 1。)


1
在可能为负的情况下,编译器无法转换x/2为。如果一个人想要的是将要计算的值,那几乎可以肯定比任何计算相同值的表达式都要快。x>>1xx>>1x/2
2013年

你是对的。如果编译器知道该值不是负数x/2x>>1则只能将其转换为。我将尝试更新我的答案。
Thomas Mueller

编译器仍然可以div通过转换x/2(x + (x<0?1:0)) >> 1(其中>>是算术右移,将符号位移位)来避免指令。这需要4条指令:复制值shr(仅获得reg中的符号位),添加sar。 goo.gl/4F8Ms4
彼得·科德斯,

该问题被标记为C和C ++。
乔什·桑福德

22

克努斯说:

过早的优化是万恶之源。

所以我建议使用 x /= 2;

这样,代码易于理解,而且我认为以这种形式进行的此操作的优化对处理器而言并没有太大的区别。


4
如果一个人希望整数保持(n + d)/ d =(n / d)+ 1?缩放图形时违反公理的行为将在结果中造成可见的“接缝”。如果有人想要一个统一且几乎对称于零的东西,那就(n+8)>>4很好了。您是否可以提供不使用已签名的右移的任何清晰或高效的方法?
2013年

19

查看编译器输出以帮助您做出决定。我在带有
gcc(GCC)4.2.1的x86-64上运行了该测试2007-0719 [FreeBSD]

另请参阅Godbolt在线上的编译器输出

您所看到的是,编译器sarl在两种情况下均使用(算术右移)指令,因此它可以识别两个表达式之间的相似性。如果使用除法,编译器还需要调整负数。为此,它将符号位向下移到最低位,然后将其添加到结果中。与除法相比,这可以解决移位负数时的一对一问题。
由于除法情况下进行了2次移位,而显式情况下仅进行了1次移位,因此我们现在可以在这里解释由其他答案测得的一些性能差异。

具有汇编输出的C代码:

对于除法,您的输入将是

int div2signed(int a) {
  return a / 2;
}

并编译为

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret

换班也一样

int shr2signed(int a) {
  return a >> 1;
}

输出:

    sarl    %edi
    movl    %edi, %eax
    ret

取决于一个人在做什么,它可能会解决“一对一”错误,或者可能导致“一对一”错误(与实际需要的相比),这将需要使用更多的代码来修复它。如果想要的是最终结果,那么右移比我所知道的任何替代方案都更快,更容易。
2013年

如果需要地板,则不太可能将自己想要的描述为“被2除”
Michael Donohue 2013年

自然数和实数的除法都遵循(n + d)/ d =(n / d)+1的公理。实数除法还支持(-n)/ d =-(n / d),这是一个自然数无意义的公理。不可能有一个除法运算符,该运算符在整数上闭合并同时支持两个公理。在我看来,说第一个公理应适用于所有数字,而第二个公理仅适用于实数,似乎比说第一个公理应适用于整数或实数而不是整数更自然。此外,我很好奇第二公理在什么情况下有用
2013年

1
满足第一个公理的整数除法会将数字线划分为大小区域d。这样的分区可用于许多目的。即使有人希望将断点设置在0到-1之间,也可以添加偏移量来移动它。满足第二个公理的整数除法会将数字线划分为大部分大小的区域d,但其中一个大小大小2*d-1。不完全是“相等”的除法。您能否提供有关奇数球分隔何时真正有用的建议?
2013年

您的shr2signed编译器输出错误。x86上的gcc选择使用算术移位(sar)实现带符号整数的>> 。goo.gl/KRgIkb。该邮件列表帖子(gcc.gnu.org/ml/gcc/2000-04/msg00152.html)确认gcc历史上对有符号的int使用算术移位,因此FreeBSD gcc 4.2.1不太可能使用无符号的移位。我更新了您的帖子以解决此问题,并在前段中说了两者都使用了shr,而实际上它们都是SAR。SHR是它为/案例提取符号位的方式。还包括一个godbolt链接。
彼得·科德斯

15

只是一个补充说明-

x * = 0.5在某些基于VM的语言中通常会更快-尤其是actionscript,因为不必检查变量是否除以0。


2
@minitech:那真是一个糟糕的考验。测试中的所有代码都是恒定的。在对代码进行JIT处理之前,它将消除所有常量。

@ M28:我很确定jsPerf的内部原理(即eval)每次都会重新发生。无论如何,是的,这是一个非常糟糕的测试,因为这是一个非常愚蠢的优化。
Ry-

13

使用x = x / 2; OR, x /= 2;因为将来可能会有新的程序员对其进行处理。因此,对于他来说,找出代码行中发生的事情将变得更加容易。每个人可能都不知道这样的优化。


12

我说的是编程竞赛的目的。通常,它们具有非常大的输入,其中除以2的次数很多次,并且已知输入为正或负。

x >> 1会比x / 2好。我通过运行一个程序进行了ideone.com的检查,该程序进行了10 ^ 10除以2的操作。对于同一程序,x / 2花费了将近5.5s,而x >> 1花费了将近2.6s。


1
对于无符号值,编译器应优化x/2x>>1。对于带符号的值,几乎所有实现都定义x>>1为具有相同的含义,x/2但当x为正数时可以更快地进行计算,并且与x/2x为负数时有用地有所不同。
2013年

12

我会说有几件事情要考虑。

  1. 位移位应该更快,因为实际上不需要特殊的计算来移位位,但是如前所述,负数存在潜在的问题。如果确保您有正数,并且正在寻找速度,那么我建议您进行移位。

  2. 除法运算符对于人类来说非常容易阅读。因此,如果您正在寻找代码可读性,则可以使用它。请注意,编译器优化领域已经走了很长一段路,因此使代码易于阅读和理解是一种好习惯。

  3. 取决于底层硬件,操作可能具有不同的速度。阿姆达尔定律是为了使普通案件快速生效。因此,您可能拥有可以比其他硬件更快地执行不同操作的硬件。例如,乘以0.5可能比除以2更快。(当然,如果要强制执行整数除法,则可能需要乘以下限)。

如果您追求纯粹的性能,我建议您创建一些可以执行数百万次操作的测试。对执行次数进行几次采样(样本量),以确定在统计学上最好的操作系统/硬件/编译器/代码中哪一种。


2
“位移位应该更快”。编译器将优化除法运算
Trevor Hickey 2012年

我希望他们会这样做,但是除非您可以访问编译器的源代码,否则不能确定:)
James Oravec 2013年

1
如果人们的实现以最常见的方式处理它,并且人们想要处理负数的方式与“做什么>>”和“不匹配”相匹配,我/也建议。
2013年

12

就CPU而言,移位操作比除法运算要快。但是,编译器知道这一点,并将在可能的范围内进行适当的优化,因此您可以以最有意义的方式进行编码,并且可以轻松地知道自己的代码正在高效地运行。但是请记住,由于先前指出的原因,unsigned int可以(在某些情况下)比int对a 进行优化。如果不需要带符号的算术,则不要包含符号位。


11

x = x / 2; 是适合使用的代码..但是操作取决于您自己的程序如何产生输出。


11

让您的意图更清楚...例如,如果要除法,请使用x / 2,并让编译器对其进行优化以移位运算符(或其他运算符)。

当今的处理器不会让这些优化对程序性能产生任何影响。


10

答案取决于您正在使用的环境。

  • 如果您使用的是8位微控制器或任何不具有硬件支持乘法功能的器件,那么移位是正常现象,而且编译器几乎可以肯定会x /= 2变成x >>= 1,而除法符号的出现会在该环境中引起更多的关注。使用班次进行除法。
  • 如果您在性能至关重要的环境或代码部分中工作,或者可以在关闭编译器优化的情况下对代码进行编译,x >>= 1则仅出于说明目的,用注释解释其原因可能是最好的。
  • 如果您不属于上述条件之一,只需使用即可使代码更具可读性x /= 2。最好节省下一位程序员,他们恰好在移位操作上花了10秒两次查看代码,而不是不必要地证明您知道移位在没有编译器优化的情况下更为有效。

所有这些都假定无符号整数。简单的转变可能不是您想要签名的。此外,DanielH还提出了有关将x *= 0.5某些语言用于ActionScript之类的好处。


8

mod 2,测试=1。不知道c中的语法。但这可能最快。


7

通常右移分为:

q = i >> n; is the same as: q = i / 2**n;

有时会以提高清晰度为目的来加速程序。我不认为你应该这样做。编译器足够聪明,可以自动执行加速。这意味着换班并不会带来任何好处,却会损害清晰度

看一下《实用C ++编程》中的此页面。


如果要计算例如(x+128)>>8将要计算的值x不接近最大值的值,那么如何在没有移位的情况下简洁地做到这一点呢?请注意,这(x+128)/256将不起作用。您知道有什么不错的表达吗?
2013年

7

显然,如果您要为下一个阅读该代码的人编写代码,请使用“ x / 2”的清晰度。

但是,如果速度是您的目标,则可以尝试两种方式并按时间排序结果。几个月前,我研究了一个位图卷积例程,该例程涉及遍历整数数组并将每个元素除以2。 / 2“。

当我实际上对两种方式都计时时,令我惊讶的是x / 2比x >> 1快

这是使用Microsoft VS2008 C ++并启用了默认优化功能。


4

在性能方面。CPU的移位操作比划分操作码要快得多。因此,除以2或乘以2等都将受益于移位运算。

至于外观。作为工程师,我们什么时候对化妆品如此着迷,甚至连漂亮的女士也不会使用!:)


3

X / Y是正确的一个...和“ >>”移位运算符。如果我们想将两个整数相除,可以使用(/)除法运算符。移位运算符用于移位位。

x = x / 2; x / = 2; 我们可以这样使用


0

虽然x >> 1比x / 2快,但是>>在处理负值时正确使用>>会稍微复杂一些。它要求类似于以下内容:

// Extension Method
public static class Global {
    public static int ShiftDivBy2(this int x) {
        return (x < 0 ? x + 1 : x) >> 1;
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.