好吧,至少有两种低级方法可以确定给定数字是否为偶数:

 1. if (num%2 == 0) { /* even */ } 
 2. if ((num&1) == 0) { /* even */ }

我认为第二种选择更加优雅和有意义,这也是我通常使用的一种。但这不仅是品味问题,也是品味问题。实际性能可能会有所不同:通常,按位运算(例如此处的逻辑与)比 mod(或 div)运算效率更高。当然,您可能会说有些编译器无论如何都能够优化它,我同意......但有些则不能。

另一点是,对于经验不足的程序员来说,第二个可能有点难以理解。对此,我的回答是,如果这些程序员花那么短的时间来理解此类语句,那么可能只会使每个人受益。

你怎么认为?

仅当以下情况时,给定的两个片段才是正确的: num 是一个无符号整数,或者是一个用二进制补码表示的负数。- 正如一些评论正确指出的那样。

有帮助吗?

解决方案

如果你会说,有些编译器不会优化%2,那么你也应该注意一些编译器使用的补代表性符号整数。在这种表示中,&1 给出错误的答案为负数。

所以,你想要什么 - 代码是“有些编译”,或代码这是不对的“一些编译器”慢?不一定是相同在每一种情况下的编译器,但两者种是极其罕见的。

当然,如果num是一个无符号类型的,或C99固定宽度整数类型(int8_t等,这需要是2的补码)之一,那么这是不是一个问题。在这种情况下,我认为%2更加优雅和有意义,并&1是一个黑客工具,可能被认为是必要的,有时性能。我认为例子CPython中并没有这样做的优化,同样将充分解释的语言真实(尽管然后解析开销可能远远大于两个机器指令之间的差)。我自己也有点意外地遇到一个C或C ++编译器,它并没有这样做在可能的情况,但是,因为它在如果不是前发出指示的点是显而易见的。

在一般情况下,我要说的是,在C ++中,你是完全在编译器的优化能力摆布。标准容器和算法有间接,当编译器完成内联和优化其中大部分消失的n层。一个体面的C ++编译器可以处理与常数值运算早餐前,和一个不正经的C ++编译器将产生的垃圾代码,无论你做什么。

其他提示

有关可读性第一,所以我选择在这里是num % 2 == 0我的代码。这比num & 1 == 0更加清晰。我让关于我的优化编译器的担心而只调整如果分析显示这是一个瓶颈。别的是不成熟的。

  

我考虑第二个选项是远更优雅和有意义

我强烈不同意此。甲数目是偶数,因为它的叠合模2是零,而不是因为它的二进制表示以一定的位结束。二进制表示是一个实现细节。依托实现细节通常是一个代码味道。正如其他人所指出的那样,测试LSB失败在使用的补交涉机。

  

另一点是,第二个可能有点难以理解为较少经验的程序员。在那我会回答,如果这些程序员利用这短短的时间来了解这种声明它可能只会有利于所有人。

我不同意。我们都应该编码,使我们的意图更加清晰。如果我们正在测试均匀化的代码应该表达(和评论应该是不必要的)。再次,检测一致性模2更清楚地表示的代码的意图不是检查LSB。

和,更重要的是,细节应在isEven方法隐藏起来。因此,我们应该看到if(isEven(someNumber)) { // details }只看到在num % 2 == 0的定义isEven一次。

我定义和使用的“ISEVEN”功能,所以我没有去想它,然后我选择了一个方法或其他的,忘记我如何检查,如果事情是偶数。

只有挑剔/要注意的是我只是说,与位运算,你假定一些有关二进制数字的表示,与模你不是。您解释为十进制值的数量。这是非常有保证的整数工作。但是考虑到模将为双,但是位运算工作不会。

您对性能的结论是基于流行的错误的前提。

由于某种原因,你硬要翻译的语言操作到他们的“明显”机器同行,并基于对翻译性能的结论。在这个特定的情况下,你的结论是,C ++语言按位和&操作必须由按位和机的操作,而模%操作必须以某种方式涉及机器的分,这是据称慢。这样的方法是很有限的用途,如果有的话。

首先,我无法想象现实生活中的C ++编译器,将解释在这样的“字面”的方式的语言的操作,即通过将它们映射到“等效”的机器操作。这主要是因为往往比一了认为等同机操作根本不存在。

当涉及到这样的基本操作与一个立即数作为操作数,任何自我尊重编译器总是立即“理解”,无论num & 1num % 2的整体num做同样的事情,这将使编译器生成绝对对于两个表达式相同的代码。当然,性能将是完全一样的。

顺便说一句,这不叫“优化”。优化,顾名思义,是当编译器决定从抽象C ++机的标准行为,以便产生更有效的代码(保留的节目的可观察行为)偏离。有在此情况下,没有偏差,这意味着没有优化。

此外,这是很有可能的是,给定计算机上的最优化的方式同时实现既不按位和也不,但其它专用机专用一些指令。最重要的是,它很可能不会有任何需要的任何指令可言,因为一个特定的值,甚至岬/奇岬可能通过处理器状态标志或者类似的东西暴露“免费”这一点。

在换句话说,效率参数无效。

其次,返回到原来的问题,更优选的方法,以确定的值的偶数岬/奇数岬肯定是num % 2方法,因为它(“通过定义”)从字面上实现所需的检查,和明确表示一个事实,即检查是纯粹的数学。即它清楚地表明,我们关心的一个的属性,而不是对的属性其表示(如将在num & 1变体的情况下)。

num & 1变体应情况下,当要获得多项的值表示的比特保留。使用此代码为偶数的烦躁/奇数岬检查是很成问题的做法。

它已经提到了很多次,任何现代编译器会产生相同的组件,这两个选项。这让我想起了 LLVM演示页面我什么地方看到过有一天,所以我想我给它一去。我知道这是不是要比传闻多得多,但它确实证实我们所期望的:x%2x&1是相同的实施

我也试图与GCC-4.2.1(gcc -S foo.c),所得组件编译这两种是相同的(并且在此答案的底部粘贴)。

程序的第一:

int main(int argc, char **argv) {
  return (argc%2==0) ? 0 : 1;
}

结果:

; ModuleID = '/tmp/webcompile/_27244_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

计划第二:

int main(int argc, char **argv) {
  return ((argc&1)==0) ? 0 : 1;
}

结果:

; ModuleID = '/tmp/webcompile/_27375_0.bc'
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind readnone {
entry:
    %0 = and i32 %argc, 1       ; <i32> [#uses=1]
    ret i32 %0
}

GCC输出:

.text
.globl _main
_main:
LFB2:
  pushq %rbp
LCFI0:
  movq  %rsp, %rbp
LCFI1:
  movl  %edi, -4(%rbp)
  movq  %rsi, -16(%rbp)
  movl  -4(%rbp), %eax
  andl  $1, %eax
  testl %eax, %eax
  setne %al
  movzbl  %al, %eax
  leave
  ret
LFE2:
  .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
  .set L$set$0,LECIE1-LSCIE1
  .long L$set$0
LSCIE1:
  .long 0x0
  .byte 0x1
  .ascii "zR\0"
  .byte 0x1
  .byte 0x78
  .byte 0x10
  .byte 0x1
  .byte 0x10
  .byte 0xc
  .byte 0x7
  .byte 0x8
  .byte 0x90
  .byte 0x1
  .align 3
LECIE1:
.globl _main.eh
_main.eh:
LSFDE1:
  .set L$set$1,LEFDE1-LASFDE1
  .long L$set$1
ASFDE1:
  .long LASFDE1-EH_frame1
  .quad LFB2-.
  .set L$set$2,LFE2-LFB2
  .quad L$set$2
  .byte 0x0
  .byte 0x4
  .set L$set$3,LCFI0-LFB2
  .long L$set$3
  .byte 0xe
  .byte 0x10
  .byte 0x86
  .byte 0x2
  .byte 0x4
  .set L$set$4,LCFI1-LCFI0
  .long L$set$4
  .byte 0xd
  .byte 0x6
  .align 3
LEFDE1:
  .subsections_via_symbols

这完全取决于上下文。其实,我更喜欢和1的方式自己,如果它是一个低的水平,系统上下文。在许多这些种上下文的“甚至是”基本上是指具有低的位零到我的,而不是由两个是整除。

但是:你的一个衬里有一个错误

您必须

if( (x&1) == 0 )

if( x&1 == 0 )

1 == 0后者与运算的x,即它与运算x,其中0,得到0,这总是计算为假当然

那么究竟你的建议,如果你做到了,所有的数字都是奇数!

任何现代编译器将优化掉模运算,因此速度不是关心的问题。

我会说使用模会使事情更容易理解,但创建使用is_even方法为您提供了两全其美的x & 1功能。

他们都非常直观。

我给一个微弱优势领先于num % 2 == 0,但我真的没有特别的偏好。当然,至于性能也越高,它可能是一个微型的优化,所以我不会担心。

我消费 坚持认为任何合理的编译器都值得其在磁盘上消耗的空间进行优化 num % 2 == 0num & 1 == 0. 。然后,通过分析不同原因的反汇编,我有机会实际验证我的假设。

事实证明,我错了。 微软视觉工作室, ,一直到版本 2013,生成以下目标代码 num % 2 == 0:

    and ecx, -2147483647        ; the parameter was passed in ECX
    jns SHORT $IsEven
    dec ecx
    or  ecx, -2
    inc ecx
$IsEven:
    neg ecx
    sbb ecx, ecx
    lea eax, DWORD PTR [ecx+1]

确实是的。这是在发布模式下,启用了所有优化。无论是针对 x86 还是 x64 进行构建,您都会获得几乎相同的结果。你可能不会相信我;我自己都几乎不相信。

它基本上符合您的期望 num & 1 == 0:

not  eax                        ; the parameter was passed in EAX
and  eax, 1

通过比较, 海湾合作委员会 (早至 v4.4)和 (早在 v3.2)就按照人们所期望的那样,为两个变体生成相同的目标代码。然而,根据 Matt Godbolt 的交互式编译器, 国际商会 13.0.1 也超出了我的预期。

当然,这些编译器不是 错误的. 。这不是一个错误。有很多技术原因(正如其他答案中充分指出的那样)为什么这两个代码片段不相同。这里肯定有一个“过早的优化是邪恶的”论点。诚然,我花了很多年才注意到这一点,这是有原因的,即便如此,我也只是错误地偶然发现了它。

但, 像道格·T.说, ,最好定义一个 IsEven 在你的库中的某个地方,所有这些小细节都正确,这样你就不必再考虑它们了——并保持你的代码可读。如果您经常以 MSVC 为目标,也许您会像我一样定义这个函数:

bool IsEven(int value)
{
    const bool result = (num & 1) == 0;
    assert(result == ((num % 2) == 0));
    return result;   
}

这两种方法都没有特别明显的人谁是新的编程。你应该有一个描述性的名称定义inline功能。你在它使用的方法并不重要(微优化很可能不会让你的程序更快明显的方式)。

反正我相信2)的速度要快得多,因为它不要求一个部门。

我不认为模使事情更易读。 <击>无论是有意义的,并且这两个版本是正确的。而在二进制计算机上存储号码,这样你就可以只使用二进制版本。

<击>

,编译器可以用高效的版本替换的模版。但是,这听起来像是prefering模的借口。

和可读性,在这个非常特殊的情况是两个版本的相同。一位读者说是新的节目甚至可能不知道,你可以使用模2来确定数量的甚至岬。读者必须根据它。他的可能甚至不知道模运算符

在推导报表背后的意义,它甚至可能是更容易阅读的二进制版本:

if( ( num & 1 ) == 0 ) { /* even */ }
if( ( 00010111b & 1 ) == 0 ) { /* even */ }
if( ( 00010110b & 1 ) == 0 ) { /* odd */ }

(I所使用的 “B” 后缀的澄清只,其不是C / C ++)

通过模版本,你必须仔细检查操作时如何在细节上定义(例如检查文档,以确保0 % 2是你所期望的)。

二进制AND更简单,并且没有歧义!

<击>只有操作者优先级可以是与二元操作一个陷阱。但它不应该是一个原因,以避免它们(有些甚至一天新的程序员需要他们无论如何)。

在这一点上,我可能只是增加了噪音,但据可读性推移,模选择更有意义。如果您的代码不可读,它实际上是无用的。

此外,除非这是被认为是真正短缺的资源(我想微控制器)的系统上运行的代码,不要试图以优化编译器的优化。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top