<快于<=吗?


1574

if( a < 901 )不是更快if( a <= 900 )

与这个简单示例并不完全相同,但是循环复杂代码的性能稍有变化。我想这与生成的机器代码有关,以防万一。


153
考虑到它的历史意义,答案的质量以及绩效中其他最重要的问题,我认为没有理由为什么要关闭(特别是不要删除,因为目前正在投票表决)这个问题。最多应将其锁定。同样,即使问题本身是错误的/天真的,它出现在书中的事实也意味着原始的错误信息存在于某个地方的“可信”来源中,因此该问题具有建设性,因为它有助于清除该错误。
杰森C

32
您从未告诉过您您指的是哪本书
Jonathon Reinhart 2014年

159
键入<比键入快两倍<=
德庆

6
8086
Joshua

7
投票数清楚地表明,有数百人过度优化。
m93a

Answers:


1703

不,在大多数架构上它不会更快。您没有指定,但是在x86上,所有整数比较通常都将在两条机器指令中实现:

  • testcmp指令,该指令集EFLAGS
  • Jcc(跳转)指令,具体取决于比较类型(和代码布局):
    • jne -如果不相等则跳转-> ZF = 0
    • jz -如果为零(等于)则跳转-> ZF = 1
    • jg -如果更大则跳转-> ZF = 0 and SF = OF
    • (等等...)

示例(为简洁起见编辑)$ gcc -m32 -S -masm=intel test.c

    if (a < b) {
        // Do something 1
    }

编译为:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jge     .L2                          ; jump if a is >= b
    ; Do something 1
.L2:

    if (a <= b) {
        // Do something 2
    }

编译为:

    mov     eax, DWORD PTR [esp+24]      ; a
    cmp     eax, DWORD PTR [esp+28]      ; b
    jg      .L5                          ; jump if a is > b
    ; Do something 2
.L5:

因此,两者之间的唯一区别是指令jgjge指令。两者将花费相同的时间。


我想指出的是,没有任何内容表明不同的跳转指令花​​费相同的时间。回答这个问题有些棘手,但是我可以提供以下信息:在《英特尔指令集参考》中,它们都按照一条通用指令分组在一起Jcc(如果满足条件则跳转)。在“ 优化参考手册 ”的附录C.延迟和吞吐量中将相同的分组在一起。

延迟 -执行内核完成形成指令的所有μop所需的时钟周期数。

吞吐量(T吞吐率) —在发布端口可以自由再次接受同一指令之前需要等待的时钟周期数。对于许多指令,一条指令的吞吐量可以大大小于其延迟

的值为Jcc

      Latency   Throughput
Jcc     N/A        0.5

关于以下脚注Jcc

7)有条件跳转指令的选择应基于第3.4.1节“分支预测优化”的建议,以提高分支的可预测性。成功预测分支后,的等待时间jcc实际上为零。

因此,英特尔文档中对Jcc任何一条指令的对待都没有别的区别。

如果考虑用于实现指令的实际电路,则可以假定在中的不同位上将存在简单的AND / OR门EFLAGS,以确定是否满足条件。这样,没有理由测试一个两位的指令所花费的时间比一个测试一个位所花费的时间要长得多或更少(忽略门传播延迟,这比时钟周期要短得多)。


编辑:浮点数

x87浮点数也是如此:(与上面的代码几乎相同,但是用double代替int。)

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
        fstp    st(0)
        seta    al                     ; Set al if above (CF=0 and ZF=0).
        test    al, al
        je      .L2
        ; Do something 1
.L2:

        fld     QWORD PTR [esp+32]
        fld     QWORD PTR [esp+40]
        fucomip st, st(1)              ; (same thing as above)
        fstp    st(0)
        setae   al                     ; Set al if above or equal (CF=0).
        test    al, al
        je      .L5
        ; Do something 2
.L5:
        leave
        ret

239
实际上@Dyppl jgjnle在同一个指令,7F:-)
乔纳森·莱因哈特

17
更不用说如果一个选项确实比另一个选项更快,那么优化器可以修改代码。
Elazar Leibovich 2012年

3
仅仅因为某种结果导致相同数量的指令并不一定意味着执行所有这些指令的总时间将是相同的。实际上,更多指令可以更快地执行。每个周期的指令数不是固定的,它取决于指令。
jontejj

22
@jontejj我对此非常了解。你还我的答案吗?我没有说明相同数量的指令,只是说它们被编译为完全相同的指令,只是一条跳转指令正在查看一个标志,而另一条跳转指令正在查看两个标志。我相信我已经提供了足够多的证据来证明它们在语义上是相同的。
乔纳森·莱因哈特

2
@jontejj您的观点很不错。为了获得尽可能多的可见性,我可能应该对其进行一些清理。感谢您的反馈。
乔纳森·莱因哈特

593

从历史上看(我们所说的是1980年代和1990年代初期),有些架构是正确的。根本问题是整数比较本质上是通过整数减法实现的。这引起以下情况。

Comparison     Subtraction
----------     -----------
A < B      --> A - B < 0
A = B      --> A - B = 0
A > B      --> A - B > 0

现在,当A < B减法必须借用高位以使减法正确时,就像您在手动进行加减时进行借位一样。此“借用”位通常称为进位位,可以通过分支指令进行测试。如果减法等于零(意味着相等),则会设置第二个零位

通常至少有两个条件转移指令,一个转移到进位,另一个转移到零位。

现在,让我们深入了解问题的核心,让我们扩展上一个表格,使其包含进位和零位结果。

Comparison     Subtraction  Carry Bit  Zero Bit
----------     -----------  ---------  --------
A < B      --> A - B < 0    0          0
A = B      --> A - B = 0    1          1
A > B      --> A - B > 0    1          0

因此,A < B可以在一条指令中实现for的分支,因为进位只有在这种情况下才是明确的,也就是说,

;; Implementation of "if (A < B) goto address;"
cmp  A, B          ;; compare A to B
bcz  address       ;; Branch if Carry is Zero to the new address

但是,如果我们想进行小于或等于的比较,则需要对零标志进行额外的检查,以发现相等的情况。

;; Implementation of "if (A <= B) goto address;"
cmp A, B           ;; compare A to B
bcz address        ;; branch if A < B
bzs address        ;; also, Branch if the Zero bit is Set

因此,在某些机器上,使用“小于”比较可能节省一条机器指令。在亚兆赫兹处理器速度和1:1 CPU与内存的速度比时代,这是相关的,但是今天几乎完全不相关了。


10
此外,x86之类的体系结构实现了诸如的指令jge,它们同时测试零和符号/进位标志。
greyfade 2012年

3
即使对于给定的架构也是如此。没有一个编译器作者会注意到并增加优化以将较慢的速度替换为较快的速度的可能性是多少?
乔恩·汉纳

8
在8080上是这样。它有指令跳到零和跳到负,但没有一个可以同时测试两者。

4
6502和65816处理器系列也是如此,该系列也扩展到了Motorola 68HC11 / 12。
卢卡斯

31
甚至在8080 <=测试中可以实现一个指令与交换操作数和测试not <(相当于>=)这是所希望的<=与交换操作数:cmp B,A; bcs addr。这就是Intel省略该测试的原因,他们认为这是多余的,并且您当时无法负担多余的指令:-)
Gunther Piez,2012年

92

假设我们正在谈论内部整数类型,则没有一种方法可以比另一种更快。它们在语义上显然是相同的。他们都要求编译器做同样的事情。只有严重损坏的编译器会为其中之一生成劣质代码。

如果有一些平台,<是速度比<=为简单的整数类型,编译器应该总是转换<=<为常数。任何不适合该平台的编译器都将是糟糕的编译器。


6
我同意+1。在编译器决定要达到的速度之前,都<没有<=速度。当您考虑到编译器通常已经执行了无效代码优化,尾部调用优化,循环提升(有时会展开),各种循环的自动并行化等工作时,这对于编译器而言是非常简单的优化。为什么浪费时间考虑过早的优化?运行原型,分析其轮廓以确定最重要的优化位于何处,按重要性顺序执行这些优化,并在测量进度的过程中再次进行轮廓分析
自闭症

在某些边缘情况下,具有一个常数值的比较在<=以下可能会更慢,例如,当从(a < C)to 转换(a <= C-1)(对于某个常数C)导致C在指令集中更难编码时。例如,在比较中,一条指令集可能能够以紧凑的形式表示从-127到128的有符号常量,但是该范围之外的常量必须使用更长,更慢的编码或完全使用另一条指令来加载。因此,像这样的比较(a < -127)可能没有直接的转换。
BeeOnRope16年

@BeeOnRope问题不是执行因其常量不同而不同的操作是否会影响性能,而是使用不同的常量表示相同的操作是否会影响性能。因此,我们没有进行比较a > 127a > 128因为您别无选择,只能使用所需的那个。我们比较a > 127a >= 128,不能要求不同的编码或不同的指令,因为它们具有相同的真值表。一个的任何编码等同于另一个的编码。
David Schwartz

我是在一个普通的方式回应你的声明说:“如果有一些平台,[<=速度较慢]编译器要经常转换<=<常量”。据我所知,这种转换涉及更改常数。例如,a <= 42被编译为a < 43因为<速度更快。在某些情况下,这样的转换不会取得成果,因为新常数可能需要更多或更慢的指令。当然a > 127a >= 128是等效的,编译器应该以(相同)最快的方式对这两种形式进行编码,但这与我所说的并不矛盾。
BeeOnRope16年

67

我看到两者都不快。编译器会在每种情况下以不同的值生成相同的机器代码。

if(a < 901)
cmpl  $900, -4(%rbp)
jg .L2

if(a <=901)
cmpl  $901, -4(%rbp)
jg .L3

我的示例if来自Linux上x86_64平台上的GCC。

编译器作家是非常聪明的人,他们认为这些东西以及我们大多数人认为理所当然的许多其他事物。

我注意到,如果不是常数,则在两种情况下都会生成相同的机器代码。

int b;
if(a < b)
cmpl  -4(%rbp), %eax
jge   .L2

if(a <=b)
cmpl  -4(%rbp), %eax
jg .L3

9
请注意,这特定于x86。
迈克尔·彼得罗塔

10
我认为您应该使用它if(a <=900)来证明它生成完全相同的asm :)
Lipis

2
@AdrianCornish对不起..我对其进行了编辑..或多或少都相同..但是,如果将第二个if更改为<= 900,那么asm代码将完全相同:)现在几乎相同。知道..对于OCD :)
Lipis 2012年

3
@Boann可能会减少为if(true)并完全消除。
Qsario 2012年

5
没有人指出这种优化仅适用于常量比较。我可以保证不会比较两个变量。
乔纳森·莱因哈特

51

对于浮点代码,即使在现代体系结构上,<=比较的确可能会更慢(通过一条指令)。这是第一个功能:

int compare_strict(double a, double b) { return a < b; }

在PowerPC上,首先执行浮点比较(更新cr条件寄存器),然后将条件寄存器移至GPR,将“小于比较”位移到适当位置,然后返回。它需要四个指令。

现在考虑使用此函数:

int compare_loose(double a, double b) { return a <= b; }

这需要与上述相同的工作compare_strict,但是现在有两个有趣的地方:“小于”和“等于”。这需要一条额外的指令(cror-条件寄存器按位或)将这两个位组合为一个。因此compare_loose需要五个指令,而同时compare_strict需要四个。

您可能会认为编译器可以像这样优化第二个功能:

int compare_loose(double a, double b) { return ! (a > b); }

但是,这将错误地处理NaN。NaN1 <= NaN2并且都NaN1 > NaN2需要评估为假。


幸运的是,它在x86(x87)上无法正常工作。fucomip设置ZF和CF。
乔纳森·莱因哈特

3
@JonathonReinhart:我想你误会什么了PowerPC在做什么-状态寄存器cr 相当于像标志ZF,并CF在x86。(尽管CR更灵活。)张贴者正在谈论的是将结果移动到GPR:在PowerPC上需要两条指令,但是x86有条件移动指令。
Dietrich Epp 2012年

@DietrichEpp我要在声明之后添加的内容是:然后您可以根据EFLAGS的值立即跳转。抱歉,不清楚。
乔纳森·莱因哈特

1
@JonathonReinhart:是的,您还可以根据CR的值立即跳转。答案不是谈论跳跃,这是额外说明的来源。
Dietrich Epp 2012年

34

也许那本未命名的书的作者阅读的a > 0速度快于普通作者,a >= 1并认为这是正确的。

但这是因为0涉及到a(因为CMP可以根据体系结构将其替换为OR),而不是因为<


1
当然,在“调试”身材,但它会采取一个坏的编译器,(a >= 1)运行慢(a > 0),因为前者可以被优化平凡转化为后者..
BeeOnRope

2
@BeeOnRope有时,我会感到惊讶,优化器可以优化哪些复杂的事情,而优化器却无法做到这一点。
glglgl

1
确实,在非常重要的几个功能中,始终值得检查asm输出。也就是说,上述转换是非常基本的,甚至在简单的编译器中也已执行了数十年。
BeeOnRope16年

32

至少,如果这是真的,则编译器可以将<= b简化为!(a> b),因此,即使比较本身实际上速度较慢,除了最幼稚的编译器之外,您都不会注意到差异。


为什么!(a> b)是<= b的优化版本。是(a> b)2次运算吗?
Abhishek Singh 2015年

6
@AbhishekSingh NOT是由其他指令(je相对jne)制作的
Pavel Gatnar

15

它们具有相同的速度。也许在某些特殊的体系结构中,他/她说的是正确的,但是至少在x86系列中,我知道它们是相同的。因为这样做,CPU将进行减法(a-b),然后检查标志寄存器的标志。该寄存器的两位分别称为ZF(零标志)和SF(符号标志),并且在一个周期内完成,因为它将用一个掩码操作来完成。


14

这将高度依赖于C编译到的基础体系结构。某些处理器和体系结构可能具有显式的指令,用于等于或小于和等于的指令,它们以不同的周期数执行。

但是,这将是非常不寻常的,因为编译器可以解决该问题,因此不相关。


1
如果循环有差异。1)将无法检测到。2)任何值得付出代价的编译器都已经可以在不更改代码含义的情况下,从慢速形式转换为快速形式。因此,植入的结果指令将是相同的。
马丁·约克

完全同意,在任何情况下,这都是微不足道的愚蠢差异。当然,在与平台无关的书中也没有提及。
泰尔金

@lttlrck:我明白了。花了我一段时间(对不起我)。不,它们不可检测,因为发生了太多其他事情,无法进行测量。处理器停顿/高速缓存未命中/信号/进程交换。因此,在正常的OS情况下,单周期级别的事物在物理上是无法测量的。如果您可以消除测量中的所有干扰(将其运行在具有板载内存且无操作系统的芯片上),那么您仍然需要担心计时器的粒度,但从理论上讲,如果运行时间足够长,您会看到一些东西。
马丁·约克

12

TL; DR答案

对于体系结构,编译器和语言的大多数组合,它不会更快。

完整答案

其他答案集中在x86架构上,我不知道ARM架构(您的示例汇编程序似乎是这样)足以对生成的代码进行特别注释,但这只是一个微优化的示例,它非常架构的具体而言,并且有可能是反优化,也有可能是优化

因此,我建议这种微优化货物崇拜编程的示例,而不是最佳软件工程实践。

某些架构中,这可能是一种优化,但我知道至少有一种架构可能是相反的。古老的Transputer体系结构仅具有等于大于或等于的机器代码指令,因此所有比较都必须从这些原语构建。

即使这样,在几乎所有情况下,编译器仍可以按以下方式对评估指令进行排序:在实践中,没有任何比较比其他任何优势。但是,最坏的情况是,可能需要添加反向指令(REV)来交换操作数堆栈中的前两项。这是一条单字节指令,需要一个周期来运行,因此开销最小。

像这样的微优化是优化还是反优化取决于您使用的特定体系结构,因此养成使用特定于体系结构的微优化的习惯通常是一个坏主意,否则您可能会本能地在不适当的情况下使用一本,而这恰恰是您所阅读的书所提倡的。


6

即使有任何区别,您也应该不会注意到差异。此外,在实践中,除非您要使用一些魔术常数,否则您将不得不做其他事情a + 1a - 1使条件成立,这从根本上来说是非常糟糕的做法。


1
有什么不好的做法?递增或递减计数器?那你怎么存储索引符号呢?
jcolebrand 2012年

5
他的意思是,如果您要比较两种变量类型。当然,如果要为循环或其他内容设置值,这是微不足道的。但是,如果x <= y,并且y是未知数,则将其“优化”为x <y + 1会比较慢
JustinDanielson 2012年

@JustinDanielson同意。更不用说丑陋,令人困惑的了
乔纳森·莱因哈特

4

您可以说该行在大多数脚本语言中都是正确的,因为多余的字符会导致代码处理稍微慢一些。但是,正如最高答案所指出的那样,它在C ++中应该没有任何作用,并且使用脚本语言完成的任何事情都可能与优化无关。


我有些不同意。在竞争性编程中,脚本语言通常提供解决问题的最快方法,但是必须应用正确的技术(阅读:优化)才能获得正确的解决方案。
Tyler Crompton 2012年

3

当我写这个答案时,我只看一般关于<vs. <=的标题问题,而不是常数a < 901vs 的具体例子a <= 900。许多编译器总是通过在<和之间进行转换来缩小常量的大小<=,例如,因为x86立即操作数的-128..127编码具有较短的1字节编码。

对于ARM尤其是AArch64,能否立即编码取决于能否将狭窄的字段旋转到单词中的任何位置。因此cmp w0, #0x00f000将是可编码的,而cmp w0, #0x00effff可能不是。因此,比较和编译时常量比较小的规则并不总是适用于AArch64。


<vs. <=通常,包括运行时可变条件

在大多数机器上的汇编语言中,与的比较<=成本与与的比较成本相同<。无论您是在其上进行分支,对其进行布尔化以创建一个0/1整数,还是将其用作无分支选择操作的谓词(例如x86 CMOV),这都适用。其他答案仅解决了问题的这一部分。

但是,这个问题与C ++运算符有关,即优化程序的输入 通常,它们都同样有效。书中的建议听起来完全是伪造的,因为编译器总是可以转换他们在asm中实现的比较。但是至少有一个例外,即使用<=会意外创建编译器无法优化的内容。

作为循环条件,在某些情况下,<=它与质上不同<,这会阻止编译器证明循环不是无限的。 这会带来很大的不同,从而禁用自动矢量化。

与有符号溢出(UB)不同,无符号溢出被定义为以2为基的环绕。使用基于未发生有符号溢出的UB进行优化的编译器进行签名循环计数器通常是安全的:++i <= size最终总是会变为false。(每个C程序员应该了解的未定义行为

void foo(unsigned size) {
    unsigned upper_bound = size - 1;  // or any calculation that could produce UINT_MAX
    for(unsigned i=0 ; i <= upper_bound ; i++)
        ...

编译器只能针对所有可能的输入值(保留那些导致不确定行为的输入值)保留C ++源的(定义的和法律上可观察的)行为的方式进行优化

(一个简单i <= size的问题也会产生问题,但我认为计算上限是一个更现实的示例,因为它意外地为您不在乎但编译器必须考虑的输入引入了无限循环的可能性。)

在这种情况下,size=0导致upper_bound=UINT_MAX,并且i <= UINT_MAX始终为true。因此,此循环对于是无限的size=0即使您作为程序员可能从未打算传递size = 0 ,编译器也必须尊重这一点。如果编译器可以将此函数内联到调用程序中,从而可以证明size = 0是不可能的,那么它很好,它可以像进行优化那样进行优化i < size

如果在循环内部不需要的实际值,则asm like if(!size) skip the loop; do{...}while(--size);是优化for( i<size )循环的一种通常有效的方法i为什么循环总是被编译为“ do ... while”样式(尾跳)?)。

但这并不能是无限的:如果输入size==0,我们将得到2 ^ n次迭代。(对for循环 C 中的所有无符号整数进行迭代使得可以对所有包含零的无符号整数表示一个循环,但要像在asm中那样没有进位标志,这并不容易。)

随着循环计数器的回绕成为可能,现代编译器通常只是“放弃”,而没有那么积极地进行优化。

示例:1到n的整数之和

使用无符号会i <= n打败clang的惯用语识别功能,该功能sum(1 .. n)基于高斯n * (n+1) / 2公式以闭合形式优化循环

unsigned sum_1_to_n_finite(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i < n+1 ; ++i)
        total += i;
    return total;
}

Godbolt编译器资源管理器中来自clang7.0和gcc8.2的x86-64 asm

 # clang7.0 -O3 closed-form
    cmp     edi, -1       # n passed in EDI: x86-64 System V calling convention
    je      .LBB1_1       # if (n == UINT_MAX) return 0;  // C++ loop runs 0 times
          # else fall through into the closed-form calc
    mov     ecx, edi         # zero-extend n into RCX
    lea     eax, [rdi - 1]   # n-1
    imul    rax, rcx         # n * (n-1)             # 64-bit
    shr     rax              # n * (n-1) / 2
    add     eax, edi         # n + (stuff / 2) = n * (n+1) / 2   # truncated to 32-bit
    ret          # computed without possible overflow of the product before right shifting
.LBB1_1:
    xor     eax, eax
    ret

但是对于幼稚的版本,我们只是从clang获得了一个哑循环。

unsigned sum_1_to_n_naive(unsigned n) {
    unsigned total = 0;
    for (unsigned i = 0 ; i<=n ; ++i)
        total += i;
    return total;
}
# clang7.0 -O3
sum_1_to_n(unsigned int):
    xor     ecx, ecx           # i = 0
    xor     eax, eax           # retval = 0
.LBB0_1:                       # do {
    add     eax, ecx             # retval += i
    add     ecx, 1               # ++1
    cmp     ecx, edi
    jbe     .LBB0_1            # } while( i<n );
    ret

GCC都不使用封闭形式,因此选择循环条件并不会真正损害它;它使用SIMD整数加法自动矢量化,i在XMM寄存器的元素中并行运行4个值。

# "naive" inner loop
.L3:
    add     eax, 1       # do {
    paddd   xmm0, xmm1    # vect_total_4.6, vect_vec_iv_.5
    paddd   xmm1, xmm2    # vect_vec_iv_.5, tmp114
    cmp     edx, eax      # bnd.1, ivtmp.14     # bound and induction-variable tmp, I think.
    ja      .L3 #,       # }while( n > i )

 "finite" inner loop
  # before the loop:
  # xmm0 = 0 = totals
  # xmm1 = {0,1,2,3} = i
  # xmm2 = set1_epi32(4)
 .L13:                # do {
    add     eax, 1       # i++
    paddd   xmm0, xmm1    # total[0..3] += i[0..3]
    paddd   xmm1, xmm2    # i[0..3] += 4
    cmp     eax, edx
    jne     .L13      # }while( i != upper_limit );

     then horizontal sum xmm0
     and peeled cleanup for the last n%3 iterations, or something.

它也有一个普通的标量循环,我认为它用于很小n的循环和/或无限循环情况。

顺便说一句,这两个循环都在循环开销上浪费了一条指令(以及Sandybridge系列CPU上的uop)。 sub eax,1/ jnz代替add eax,1/ cmp / jcc会更有效。1个uop而不是2个(在sub / jcc或cmp / jcc宏融合之后)。两个循环之后的代码无条件地写入EAX,因此它没有使用循环计数器的最终值。


漂亮的人为例子。关于使用EFLAGS可能会对无序执行产生潜在影响的其他评论呢?纯粹是理论上的,还是JB会比JBE导致更好的流水线?
rustyx

@rustyx:我是否在其他答案下的某处发表了评论?编译器不会发出导致局部标志停顿的代码,并且肯定不会导致C <<=。但肯定的是,test ecx,ecx/ bt eax, 3/ jbe将如果ZF设置(ECX == 0),或者如果CF设置(位EAX的3 == 1)跳,导致大部分CPU的部分标志停顿,因为标志它读取并不都来自最后一条写入任何标志的指令。在Sandybridge系列上,它实际上并没有停滞,只需要插入一个合并的uop。 cmp/ test写入所有标志,但bt保留ZF不变。felixcloutier.com/x86/bt
彼得·科德斯

2

仅当创建计算机的人对布尔逻辑不好时。他们不应该是。

每个比较(>= <= > <)都可以相同的速度进行。

每个比较的结果只是一个减法(差),然后看它是否为正/负。
(如果msb设置,则数字为负)

如何检查a >= b?子a-b >= 0检查是否a-b为正。
如何检查a <= b?子0 <= b-a检查是否b-a为正。
如何检查a < b?子a-b < 0检查是否a-b为负。
如何检查a > b?子0 > b-a检查是否b-a为负。

简而言之,计算机可以针对给定的操作在幕后进行此操作:

a >= b== msb(a-b)==0
a <= b== msb(b-a)==0
a > b== msb(b-a)==1
a < b==msb(a-b)==1

当然还有电脑会实际上并不需要做==0==1两种。
因为==0它可以msb将电路中的信号反相。

无论如何,它们肯定不会a >= b被计算为a>b || a==b大声笑

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.