ChatGPT解决这个技术问题 Extra ChatGPT

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

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

技术1:

x = x >> 1;

技术2:

x = x / 2;

这里 x 是一个整数。

如果您真的想再次将结果分配给 x,那么这种方式都不合适:它应该是 x >>= 1x /= 2,这取决于您打算用操作表达什么。不是因为它更快(任何现代编译器都会将所有等效变体编译为相同的快速汇编),而是因为它不那么令人困惑。
我不同意leftaroundabout。 - 但我认为值得注意的是,在许多编程语言中有一个名为 arithmetic shift 的操作将符号位保持在适当的位置,因此可以按预期处理有符号值。语法可能类似于 x = x >>> 1。另请注意,根据平台和编译器,使用移位手动优化除法和乘法可能是相当合理的。 - 考虑微控制器,例如,不支持乘法运算的直接 ALU。
我更喜欢 x /= 2 因为 x >>= 1 看起来太像 monadic bind ;)
@leftaroundabout - 我只是认为写 x = x / 2 而不是 x /= 2 更具可读性。主观偏好也许:)
@HannoBinder:当然是主观的,尤其是很多习惯。 IMO,在所有算术运算符都有 ⬜= 组合的语言中,应尽可能使用这些组合。它消除了噪音并强调 x修改这一事实,而一般的 = 运算符则建议它采用独立于旧值的全新值。 — 始终避免使用组合运算符(以便它易于阅读,因此只知道数学运算符的人)可能也有其意义,但是您也需要放弃非常有用的 ++--+= .

M
Mark Byers

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

如果您将数字视为位序列,请使用 bitshift。

如果您将其视为数值,请使用除法。

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

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

(ideone)


最初的问题对“最佳”一词也含糊不清。在速度、可读性、欺骗学生的试题等方面的“最佳”……在没有解释“最佳”的含义的情况下,这似乎是最正确的答案。
在 C++03 中,两者都是为负数定义的实现,并且可能给出相同的结果。在 C++11 中,对负数定义了除法,但移位仍然是实现定义的。
虽然 / 的定义是早期 C 标准中定义的实现(如果对负数进行向上或向下舍入)。它必须始终与 %(模数/余数运算符)一致。
“定义的实现”意味着编译器的实现者必须在几个实现选项中进行选择,通常有很大的限制。这里,一个约束是 %/ 运算符对于正数和负数操作数必须是一致的,因此无论 ab 的符号如何,(a/b)*b+(a%b)==a 都为真。通常,作者会做出使 CPU 发挥最佳性能的选择。
所以说“编译器无论如何都会将其转换为移位”的每个人都是错误的,对吧?除非编译器可以保证您处理的是非负整数(它是常量或无符号整数),否则它不能将其更改为移位
S
Shahbaz

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


许多编译器不会将除以 2 的幂转换为位移。对于有符号整数,这将是一个不正确的优化。您应该尝试查看编译器的汇编输出并亲自查看。
IIRC 我用它来使 CUDA 上的并行减少更快(避免整数 div)。然而这是一年多以前的事了,我想知道现在的 CUDA 编译器有多聪明。
@exDM69:许多编译器即使对有符号整数也会这样做,并根据符号进行调整。玩这些东西的好工具是:tinyurl.com/6uww253
@exDM69:这很重要,怎么样?我说的是“如果可能”,而不是“总是”。如果优化不正确,那么手动执行并不能使其正确(另外,如上所述,GCC 足够聪明,可以找出有符号整数的正确替换)。
查看 WikiPedia 页面,这显然是有争议的,但我不会称其为强度降低。强度降低是指在循环中,通过将循环中的先前值相加,从乘法减少到加法。这更像是一个窥孔优化,编译器可以非常可靠地完成。
C
Community

继续说:有很多理由支持使用 x = x / 2; 这里有一些:

它更清楚地表达了您的意图(假设您没有处理位旋转寄存器位或其他东西)

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

即使编译器没有减少它并选择了比 shift 更慢的操作,这最终以可测量的方式影响程序性能的可能性本身也非常小(如果它确实对它产生了可测量的影响,那么你有一个实际的使用轮班的理由)

如果除法将成为更大表达式的一部分,则如果使用除法运算符,则更有可能获得正确的优先级:x = x / 2 + 5; x = x >> 1 + 5; // 和上面不一样

有符号算术可能比上面提到的优先级问题更复杂

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

简而言之,当你真的想要乘法或除法时,你编写一个班次代码什么都没有,除非可能会增加引入错误的可能性。这是一生,因为编译器不够聪明,无法在适当的时候将这种事情优化为转变。


还值得补充的是,虽然有优先规则,但使用括号并没有什么问题。在修改一些生产代码时,我实际上看到了 a/b/c*d 形式的东西(其中 a..d 表示数字变量)而不是更具可读性的 (a*d)/(b*c)
性能和优化取决于编译器和目标。例如,我为一个微控制器做了一些工作,除非你购买商业编译器,否则任何高于 -O0 的东西都被禁用,所以编译器肯定不会将除法转换为位移。此外,在这个目标上,位移需要一个周期,除法需要 18 个周期,并且由于微控制器时钟速度非常低,这可能确实是一个明显的性能损失(但这取决于你的代码 - 你绝对应该使用 / 直到分析告诉你这是个问题!)
@JackManey,如果 a*db*c 有可能产生溢出,那么可读性较差的形式是不等价的,并且具有明显的优势。 PS我同意括号是你最好的朋友。
@MarkRansom - 一个公平的观点(即使我在 R 代码中遇到 a/b/c*d - 在溢出意味着数据存在严重错误的上下文中 - 而不是在 C 的性能关键块中代码)。
只有当 x 永远不会是奇数负数或不关心逐个错误时,代码 x=x/2; 才比 x>>=1“更清晰”。否则 x=x/2;x>>=1; 有不同的含义。如果需要的是 x>>=1 计算的值,我会认为它比 x = (x & ~1)/2x = (x < 0) ? (x-1)/2 : x/2 或任何其他我能想到的使用除以二的公式更清楚。同样,如果需要 x/=2 计算的值,则比 ((x + ((unsigned)x>>31)>>1) 更清楚。
L
Luchian Grigore

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

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

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

如果您想将一个数字除以 2,请使用第二个数字。

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

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


j
justin

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


编译器应该进行相应的优化。
如果编译器没有相应地优化,你应该使用更好的编译器。
@DavidStone:编译器可以在哪些处理器上优化可能为负的有符号整数除以除 1 以外的任何常数,使其与移位一样有效?
@supercat:这是一个很好的观点。您当然可以将值存储在无符号整数中(我觉得与有符号/无符号不匹配警告相结合时的声誉要差得多),并且大多数编译器还有一种方法告诉他们在优化时假设某些事情是正确的.我更喜欢将它包装在一个兼容性宏中,并在 x >>= 1; 上使用 ASSUME(x >= 0); x /= 2; 之类的东西,但这仍然是一个重要的问题。
j
jamesdlin

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

但是,首选 x / 2 而不是 x >> 1 的另一个原因是,如果 x 是带符号的 int 并且为负数,则 >> 的行为取决于实现。

从 ISO C99 标准的第 6.5.7 节第 5 点:

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


值得注意的是,许多实现为 x>>scalepower 在负数上定义的行为正是将一个值除以 2 的幂以用于屏幕渲染等目的时所需要的行为,而使用 x/scalefactor 将是错误的,除非对负值应用更正。
实际上,几乎(?)所有实现都将有符号整数类型上的 >> 定义为算术右移,即移入符号位的副本以获取 2 的补码。 (例如 GNU C 保证:gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html.)当然,Deathstation 9000 可以做一些无用的事情,但这对于大多数代码来说并不是真正重要的可移植性问题。 ISO C++ 甚至朝着将其标准化为算术右移的方向发展。
T
Thomas Mueller

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

甚至 x / 2 也可能不会使用(慢)除法 CPU 指令,因为 some shortcuts are possible,但它仍然比 x >> 1 慢。

(这是一个 C / C++ 问题,其他编程语言有更多的运算符。对于 Java 也有无符号右移 x >>> 1,这又是不同的。它允许正确计算两个值的平均值(平均值),这样即使 ab 的值非常大,(a + b) >>> 1 也会返回平均值。例如,如果数组索引可以变得非常大,则对于二进制搜索来说这是必需的。有 a bug in many versions of binary search,因为它们使用(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 中的符号位)、添加、sar。 goo.gl/4F8Ms4
该问题被标记为 C 和 C++。
I
Ivan Californias

克努特说:

过早的优化是万恶之源。

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

这样代码很容易理解,而且我认为以这种形式优化这个操作,对处理器来说并没有太大的区别。


如果希望整数支持 (n+d)/d = (n/d)+ 的公理(适用于自然数和实数),您会认为将数字缩小 2 的幂的首选方法是什么1?缩放图形时违反公理将导致结果中出现可见的“接缝”。如果一个人想要一个均匀且几乎关于零对称的东西,(n+8)>>4 可以很好地工作。您能否提供任何不使用带符号右移的清晰或高效的方法?
P
Peter Cordes

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

另见compiler outputs online at 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

其他 ISA 可以同样有效地做到这一点,甚至更多。例如 AArch64 的 GCC 使用:

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

取决于一个人正在做什么,它可能会修复一个错误,或者它可能会导致一个错误(与实际需要的相比),这将需要使用进一步的代码来修复它。如果一个人想要的是一个下限的结果,那么右移比我所知道的任何替代方法都更快、更容易。
如果您需要一个地板,您不太可能将您想要的内容描述为“除以 2”
自然数和实数的除法都支持 (n+d)/d = (n/d)+1 的公理。实数除法也支持 (-n)/d = -(n/d),这是一个对自然数毫无意义的公理。不可能有一个对整数封闭并同时支持这两个公理的除法运算符。在我看来,说第一个公理应该适用于所有数字而第二个公理只适用于实数似乎比说第一个公理应该适用于整数或实数而不适用于整数更自然。此外,我很好奇第二个公理在什么情况下真正有用。
满足第一个公理的整数除法方法会将数轴划分为大小为 d 的区域。这种划分对许多目的很有用。即使人们宁愿在 0 和 -1 之间以外的地方设置断点,添加偏移量也会移动它。满足第二个公理的整数除法会将数轴划分为大部分大小为 d 的区域,但其中一个大小为 2*d-1。不完全“相等”的划分。你能提供关于oddball分区何时真正有用的建议吗?
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链接。
a
ansiart

只是一个补充说明 -

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


@minitech:这是一个糟糕的测试。测试中的所有代码都是不变的。在代码被 JITed 之前,它会消除所有的常量。
@M28:我很确定 jsPerf 的内部结构(即 eval)每次都会重新发生这种情况。无论如何,是的,这是一个非常糟糕的测试,因为它是一个非常愚蠢的优化。
I
Imdad

使用 x = x / 2;x /= 2; 因为将来可能会有新程序员使用它。所以他会更容易找出代码行中发生了什么。大家可能不知道这样的优化。


S
Shashwat Kumar

我说的是编程竞赛的目的。通常,它们具有非常大的输入,其中除以 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 有用地不同。
J
James Oravec

我想说有几件事需要考虑。

位移应该更快,因为实际上不需要特殊的计算来位移位,但是正如所指出的,负数存在潜在的问题。如果您确定有正数,并且正在寻找速度,那么我建议您使用 bitshift。除法运算符对人类来说非常容易阅读。因此,如果您正在寻找代码可读性,您可以使用它。请注意,编译器优化领域已经取得了长足的进步,因此使代码易于阅读和理解是一种很好的做法。根据底层硬件,操作可能有不同的速度。 Amdal 法则是让普通案件快速解决。因此,您可能拥有比其他硬件更快地执行不同操作的硬件。例如,乘以 0.5 可能比除以 2 更快。(当然,如果您希望强制执行整数除法,则可能需要乘法的下限)。

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


“位移应该更快”。编译器将优化划分为位移
我希望他们会,但是除非您可以访问编译器的源代码,否则您无法确定:)
如果一个人的实现以最常见的方式处理它,并且一个人想要处理负数的方式与 >> 所做的匹配并且与 / 所做的不匹配,我也会推荐 bitshift。
t
tylerl

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


G
Gooner

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


A
Atul Kumar

让你的意图更清楚......例如,如果你想除法,使用 x / 2,并让编译器优化它以移位运算符(或其他任何东西)。

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


g
gkimsey

这个问题的答案将取决于你工作的环境。

如果您正在使用 8 位微控制器或任何没有硬件支持乘法的东西,移位是预期的并且司空见惯,虽然编译器几乎肯定会将 x /= 2 转换为 x >>= 1,但除法的存在在那种环境中,符号会比使用移位来实现除法更令人惊讶。

如果您在性能关键的环境或代码部分中工作,或者您的代码可以在关闭编译器优化的情况下编译,x >>= 1 并带有解释其推理的注释可能只是为了清楚目的而最好的。

如果您不属于上述任何一种情况,只需使用 x /= 2 即可使您的代码更具可读性。下一个碰巧查看您的代码的程序员可以节省 10 秒重复操作的 shift 操作,而不是不必要地证明你知道这种转变是更有效的无编译器优化。

所有这些都假设无符号整数。简单的转变可能不是您想要的签名。此外,DanielH 提出了一个关于将 x *= 0.5 用于某些语言(如 ActionScript)的好观点。


c
circusdei

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


M
Mouna Cheikhna

一般来说,右移分为:

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

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

看看这个page from Practical C++ Programming.


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

显然,如果您正在为下一个阅读它的人编写代码,请确保“x/2”的清晰性。

但是,如果速度是您的目标,请尝试两种方式并计算结果的时间。几个月前,我研究了一个位图卷积例程,其中涉及遍历整数数组并将每个元素除以 2。我做了各种优化它,包括用“x>>1”替换“x”的老技巧/2"。

当我实际上对两种方式进行计时时,我惊讶地发现 x/2 比 x>>1 快

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


F
Farjad

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

至于外观和感觉。作为工程师,我们什么时候变得如此痴迷于连美女都不用的化妆品! :)


c
chocolate boy

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

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