哪个是用于将整数除以 2 的更好选择

Which is better option to use for dividing an integer number by 2?

本文关键字:更好 选择 整数 用于      更新时间:2023-10-16

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

技术1:

x = x >> 1;

技术2:

x = x / 2;

这里x是一个整数。

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

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

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

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

(同上(

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

堆积:有很多理由支持使用x = x / 2; 以下是一些:

  • 它更清楚地表达了您的意图(假设您不是在处理位微调寄存器位或其他东西(

  • 无论如何,编译器都会将其简化为移位操作

  • 即使编译器没有减少它并选择比移位慢的操作,这最终以可衡量的方式影响程序性能的可能性本身也微乎其微(如果它确实对它产生了可衡量的影响,那么你就有实际的理由使用移位(

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

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
    
  • 有符号算术可能比上面提到的优先级问题更复杂

  • 重申一下 - 无论如何,编译器已经为您执行此操作。事实上,它会将除以常数转换为各种数字的一系列移位、加法和乘法,而不仅仅是 2 的幂。有关此内容的更多信息的链接,请参阅此问题。

简而言之,当你真正想乘法或除法时,你不会通过编码班次来购买任何东西,除了可能增加引入错误的可能性。自从编译器不够聪明,无法在适当的时候将这种事情优化为转变以来,已经有一辈子了。

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

取决于你所说的最好是什么意思。

如果你想让你的同事讨厌你,或者让你的代码难以阅读,我肯定会选择第一个选项。

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

两者不等价,如果数字为负数或在较大的表达式中,它们的行为就不一样了 - 位移的优先级低于+-,除法的优先级更高。

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

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

我同意你应该支持x / 2的其他答案,因为它的意图更清晰,编译器应该为你优化它。

但是,首选x / 2而不是x >> 1的另一个原因是,如果x是已签名的int并且是负的,则>>的行为依赖于实现。

来自ISO C99标准第6.5.7节第5项:

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

x / 2

清晰,x >> 1也快不了多少(根据微基准测试,Java JVM快约30%(。正如其他人所指出的,对于负数,舍入略有不同,因此当您要处理负数时,必须考虑这一点。一些编译器可能会自动将x / 2转换为x >> 1,如果他们知道数字不能是负数(甚至认为我无法验证这一点(。

即使x / 2也可能不使用(慢(除法CPU指令,因为可以使用一些快捷方式,但它仍然比x >> 1慢。

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

Knuth说:

过早优化是万恶之源。

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

这样代码很容易理解,而且我认为以这种形式优化此操作并不意味着对处理器有太大影响。

查看编译器输出以帮助您做出决定。 我在 x86-64 上运行了这个测试,
gcc (GCC( 4.2.1 20070719 [FreeBSD]

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

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

带有程序集输出的 C 代码:

对于除法,您的输入将是

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

这编译为

    movl    %edi, %eax
    shrl    $31, %eax            # (unsigned)x >> 31
    addl    %edi, %eax           # tmp = x + (x<0)
    sarl    %eax                 # (x + 0 or 1) >> 1  arithmetic right shift
    ret

同样对于移位

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

带输出:

    sarl    %edi
    movl    %edi, %eax
    ret

其他国际审计机构可以同样有效地做到这一点,如果不是更有效的话。 例如,GCC for AArch64 使用:

        add     w0, w0, w0, lsr 31      // x += (unsigned)x>>31
        asr     w0, w0, 1               // x >>= 1
        ret

只是一个补充说明 -

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

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

X>>1 将优于 X/2。我通过运行一个程序来检查 ideone.com,该程序发生了超过 10^10 除以 2 次操作。X/2 花了近 5.5 秒,而 X>>1 在同一程序中花了近 2.6 秒。

我想

说有几件事需要考虑。

  1. Bitshift应该更快,因为实际上没有特殊的计算 需要移动位,但是正如所指出的,有 负数的潜在问题。如果您确信拥有 正数,并且正在寻找速度,那么我会推荐 位移。

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

  3. 根据底层硬件, 操作可能具有不同的速度。阿姆达尔定律是使 常见情况快速。因此,您可能拥有可以执行的硬件 不同的操作比其他操作更快。例如,乘以 0.5 可能比除以 2 快。(当然,如果您希望强制执行整数除法,您可能需要进行乘法(。

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

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

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

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。

一般右移位划分:

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

这有时用于以牺牲清晰度为代价来加速程序。我认为你不应该这样做.编译器足够智能,可以自动执行加速。这意味着,换班不会以牺牲清晰度为代价。

看看这个页面 实用C++编程.

显然,如果你正在为下一个阅读它的人编写代码,那就追求"x/2"的清晰度。

但是,如果速度是您的目标,请同时尝试两种方式并计时结果。几个月前,我研究了一个位图卷积例程,它涉及单步执行整数数组并将每个元素除以 2。我做了各种各样的事情来优化它,包括用"x>>1"代替"x/2"的老把戏。

当我实际计时时,我惊讶地发现 x/2 比 x>>1 快

这是使用Microsoft VS2008 C++,并打开了默认优化。

在性能方面。CPU 的移位操作明显快于除法操作码。因此,除以 2 或乘以 2 等都受益于轮班操作。

至于外观和感觉。作为工程师,我们什么时候变得如此依恋化妆品,即使是美丽的女士也不会使用!:)

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

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