您会使用num%2或num&1来检查数字是否均匀?

rmn*_*rmn 20 c++ numbers readability bitwise-operators low-level

那么,至少有两种低级方法可以确定给定的数字是否是偶数:

 1. if (num%2 == 0) { /* even */ } 
 2. if ((num&1) == 0) { /* even */ }
Run Code Online (Sandbox Code Playgroud)

我认为第二种选择更加优雅和有意义,而这正是我经常使用的选择.但这不仅仅是品味问题; 实际性能可能会有所不同:通常按位操作(例如logial和here)比mod(或div)操作更有效.当然,你可能会争辩说有些编译器无论如何都能优化它,我同意......但有些人不会.

另一点是,对于经验不足的程序员来说,第二个可能有点难以理解.关于这一点,我回答说,如果这些程序员花很短的时间来理解这种语句,它可能只会让每个人受益.

你怎么看?

只有当num无符号整数或具有二进制补码表示的负数时,给定的两个片段才是正确的. - 正如一些评论所说的那样.

jas*_*son 76

我首先编码可读性,所以我的选择是num % 2 == 0.这比清楚得多num & 1 == 0.我会让编译器担心我的优化,只有在分析显示这是一个瓶颈的情况下才会调整.其他任何事情都为时过早.

我认为第二种选择更加优雅和有意义

我强烈反对这一点.一个数字甚至是因为它​​的模2的一致性为零,而不是因为它的二进制表示以某一位结尾.二进制表示是一个实现细节.依赖于实现细节通常是代码味道.正如其他人所指出的那样,在使用补码表示的机器上测试LSB失败了.

另一点是,对于经验不足的程序员来说,第二个可能有点难以理解.关于这一点,我回答说,如果这些程序员花很短的时间来理解这种语句,它可能只会让每个人受益.

我不同意.我们都应该编码以使我们的意图更清晰.如果我们正在测试均匀性,代码应该表达(并且评论应该是不必要的).同样,测试一致性模2更清楚地表达了代码的意图而不是检查LSB.

而且,更重要的是,细节应隐藏在isEven方法中.所以我们应该看到if(isEven(someNumber)) { // details }并且只num % 2 == 0在定义中看到一次isEven.

  • 确实.如果你正在检查最低位,我的第一个假设是你正在测试一个标志. (16认同)
  • @frunsi:偶数的定义是一个可被2整除的数字.也就是说,一个可被2整除的数字,其余为零.也就是说,一个与零模2一致的数.均匀性的定义与特定基数中的数字表示无关(比如它以小数结尾的"0","2","4","6"或"8"结束,或者`二进制0`).定义的结果是偶数的LSB等于零. (11认同)
  • @frusni:错了.-1的补码最后一位为0. (9认同)
  • 数字*是*甚至因为它的二进制表示以某一位结束.它没有任何问题,没有什么能使它变得不那么真实. (2认同)

Ste*_*sop 24

如果你要说一些编译器不会优化%2,那么你还应该注意到一些编译器使用一个用于有符号整数的补码表示.在该表示中,&1 给出负数的错误答案.

那么你想要什么 - "一些编译器"的代码很慢,或者"某些编译器"的代码是错误的?在每种情况下不一定是相同的编译器,但这两种都非常罕见.

当然,如果num是无符号类型,或者是C99固定宽度整数类型之一(int8_t等等,需要2的补码),那么这不是问题.在这种情况下,我认为%2是更优雅和有意义的,并且&1可能是有时可能需要表现的黑客.我认为例如CPython不进行这种优化,完全解释的语言也是如此(尽管解析开销可能使两个机器指令之间的差异相形见绌).虽然碰到C或C++编译器并没有尽可能地做到这一点,但我会感到有点惊讶,因为如果不是之前发布指令,它就是一个明智的选择.

一般来说,我会说在C++中你完全受编译器优化能力的支配.标准容器和算法具有n级间接,其中大部分在编译器完成内联和优化后消失.一个不错的C++编译器可以在早餐之前处理具有常量值的算术,并且不管你做什么,不合适的C++编译器都会产生垃圾代码.

  • 它是.也许你不想要它,但是如果你愿意的话,每当我看到有人说"这取决于编译器"时,我会告诉你一些依赖于实现的东西,你可以度过余生24/7纠正所有;-).无论如何,在这种情况下,签名表示是依赖于实现的,正如您正确指出的那样,无论目标体系结构如何,编译器都可以做任何想做的事情.一种选择可能比另一种选择快得多. (4认同)

Dou*_* T. 12

我定义并使用"IsEven"函数,所以我不必考虑它,然后我选择了一种方法或另一种方法而忘记了如何检查是否有问题.

只有nitpick/caveat我只是说通过按位操作,你假设一些二进制数字的表示,模数你不是.您将数字解释为十进制值.这几乎可以保证与整数一起使用.但是请考虑模数对于double是有效的,但是按位运算不会.


AnT*_*AnT 12

关于性能的结论是基于流行的错误前提.

出于某种原因,您坚持将语言操作转换为"明显的"机器对应,并根据该翻译得出性能结论.在这种特殊情况下,您得出结论,&C++语言的按位和操作必须通过按位和机器操作来实现,而模%运算必须以某种方式涉及机器分割,据称速度较慢.如果有的话,这种方法的用途非常有限.

首先,我无法想象一个真实的C++编译器会解释这样的"文字"的方式的语言操作,将其映射到"等效"机器操作IE浏览器.大多数情况下,你认为等效的机器操作根本就不存在.

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

顺便说一句,这不称为"优化".根据定义,优化是指编译器决定偏离抽象C++机器的标准行为以生成更有效的代码(保留程序的可观察行为).在这种情况下没有偏差,这意味着没有优化.

而且,很可能在给定的机器上实现两者的最佳方式既不是按位也不是除法,而是一些其他专用的机器专用指令.最重要的是,它很可能有不会有任何需要任何指令可言,因为即使岬/一特定值的奇岬可能通过处理器状态标志或者类似的东西暴露了"自由"那.

换句话说,效率参数无效.

其次,要回到原来的问题,更可取的方法来确定一个值的偶数内斯/奇岬肯定是num % 2方法,因为它("定义")的字面实现所需的检查,并明确表达了这一事实这张支票纯粹是数学的.即它清楚地表明我们关心数字的属性,而不是关于其表示的属性(如num & 1变体的情况).

num & 1变种应情况下,当你想获得多项的值表示的位保留.使用此代码进行偶数/奇数检查是一个非常值得怀疑的做法.

  • 你在这里做了很多假设,并非所有假设都是正确的,但是你的态度让你获得-1.这是一个简单的问题,你不必暗杀OP. (7认同)
  • 此外,X86是一种体系结构,其中值的奇数通过PF CPU标志公开,这意味着如果值是作为上一次操作的结果获得的话,智能编译器可能根本不会生成任何指令. (3认同)
  • 首先,它是一个简单的答案的简单问题.如果你想要的话它只会很复杂.第二,你的最后一篇文章,你自相矛盾(我所做的大多数陈述都过于通用而不能被称为"错误的假设"./"人类历史上没有C++编译器"是具体到它可以得到的),你试图过度补偿和贬低(对于那些在理解问题上缺乏足够深度的人来说,这只是一个"简单问题")并且通常是粗鲁的,完全掩盖了你所做的任何正确陈述.我建议你照镜子. (3认同)
  • 我+ 1'这个.关于*值*操作和*值表示*操作操作的差异的很好的解释.它还包含"straigth-forward"参数和"你不知道CPU"参数. (3认同)
  • 我所做的大多数陈述过于笼统,因此不能称为“不正确的假设”。所以:对不起,我所说的一切都是正确的。如果您觉得有些不正确,则必须更加具体。至于态度,我敢肯定,您正在想象中没有的东西。 (2认同)

mrk*_*rkj 9

已经多次提到任何现代编译器都会为这两个选项创建相同的程序集.这让我想起了前几天我在某处看到的LLVM演示页面,所以我想我会试一试.我知道这不仅仅是轶事,但它确实证实了我们的期望:x%2并且x&1实现相同.

我也尝试用gcc-4.2.1(gcc -S foo.c)编译这两个,并且结果汇编是相同的(并粘贴在这个答案的底部).

编程第一:

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

结果:

; 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
}
Run Code Online (Sandbox Code Playgroud)

编程第二个:

int main(int argc, char **argv) {
  return ((argc&1)==0) ? 0 : 1;
}
Run Code Online (Sandbox Code Playgroud)

结果:

; 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
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


Bil*_*ter 7

这一切都取决于背景.如果它是一个低级别的系统环境,我实际上更喜欢&1方法.在许多这样的情境中,"甚至"基本上意味着对我来说零位为零,而不是被两个整除.

但是:你的一个班轮有一个错误.

你必须去

if( (x&1) == 0 )
Run Code Online (Sandbox Code Playgroud)

if( x&1 == 0 )
Run Code Online (Sandbox Code Playgroud)

后者ANDs x与1 == 0,即ANDs x为0,产生0,当然总是评估为false.

所以,如果你完全按照你的建议去做,那么所有的数字都很奇怪!

  • 我想这是'%2`的一个原因:`%`的优先级在C中更直观. (3认同)
  • 是的,我发现这是一个优先规则,并不像我期望的那样,所以我总是小心翼翼.在一些体面的调试器之前的早期,它让我感到很难受,花费善良知道需要多少小时.我发现我的答案很快就发现这个问题很快被编辑了. (2认同)