您会使用 num%2 或 num&1 来检查数字是否为偶数吗?
好吧,至少有两种低级方法可以确定给定的数字是否为偶数:
1. if (num%2 == 0) { /* even */ }
2. if ((num&1) == 0) { /* even */ }
我认为第二个选项更加优雅和有意义,这就是我通常使用的方法。但这不仅仅是品味问题;实际性能可能会有所不同:通常按位运算(例如此处的逻辑与)比 mod(或 div)运算效率更高。当然,您可能会说有些编译器无论如何都能够优化它,我同意......但有些则不能。
另一点是,对于经验不足的程序员来说,第二个可能有点难以理解。对此,我的回答是,如果这些程序员花那么短的时间来理解此类语句,那么可能只会使每个人受益。
你怎么认为?
仅当 num
是无符号整数或具有二进制补码表示形式的负数时,给定的两个片段才是正确的。 - 正如一些评论正确指出的那样。
Well, there are at least two low-level ways of determining whether a given number is even or not:
1. if (num%2 == 0) { /* even */ }
2. if ((num&1) == 0) { /* even */ }
I consider the second option to be far more elegant and meaningful, and that's the one I usually use. But it is not only a matter of taste; The actual performance may vary: usually the bitwise operations (such as the logial-and here) are far more efficient than a mod (or div) operation. Of course, you may argue that some compilers will be able to optimize it anyway, and I agree...but some won't.
Another point is that the second one might be a little harder to comprehend for less experienced programmers. On that I'd answer that it will probably only benefit everybody if these programmers take that short time to understand statements of this kind.
What do you think?
The given two snippets are correct only if num
is either an unsigned int, or a negative number with a two's complement representation. - As some comments righfuly state.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
我首先为了可读性而编码,所以我这里的选择是
num % 2 == 0
。这比num & 清楚得多。 1 == 0 。我会让编译器为我担心优化,并且仅在分析显示这是一个瓶颈时进行调整。任何其他事情都为时过早。
我强烈不同意这一点。数字是偶数是因为它的同余模二为零,而不是因为它的二进制表示以某个位结尾。二进制表示是一个实现细节。依赖实现细节通常是一种代码味道。正如其他人指出的那样,在使用补码表示的机器上测试 LSB 会失败。
我不同意。我们都应该通过编码来使我们的意图更加清晰。如果我们正在测试均匀性,代码应该表达这一点(并且注释应该是不必要的)。同样,测试模 2 的一致性比检查 LSB 更能清楚地表达代码的意图。
而且,更重要的是,细节应该隐藏在 isEven 方法中。所以我们应该看到
if(isEven(someNumber)) { // details }
并且在isEven
的定义中只看到一次num % 2 == 0
代码>.I code for readability first so my choice here is
num % 2 == 0
. This is far more clear thannum & 1 == 0
. I'll let the compiler worry about optimizing for me and only adjust if profiling shows this to be a bottleneck. Anything else is premature.I strongly disagree with this. A number is even because its congruency modulo two is zero, not because its binary representation ends with a certain bit. Binary representations are an implementation detail. Relying on implementation details is generally a code smell. As others have pointed out, testing the LSB fails on machines that use ones' complement representations.
I disagree. We should all be coding to make our intent clearer. If we are testing for evenness the code should express that (and a comment should be unnecessary). Again, testing congruency modulo two more clearly expresses the intent of the code than checking the LSB.
And, more importantly, the details should be hidden away in an
isEven
method. So we should seeif(isEven(someNumber)) { // details }
and only seenum % 2 == 0
once in the definition ofisEven
.如果您要说某些编译器不会优化
%2
,那么您还应该注意某些编译器对有符号整数使用补码表示。在该表示中,&1
对于负数给出了错误的答案。那么你想要什么 - 在“某些编译器”上运行缓慢的代码,或者在“某些编译器”上错误的代码?每种情况下不一定使用相同的编译器,但这两种类型都极为罕见。
当然,如果
num
是无符号类型,或者C99定宽整数类型之一(int8_t
等,要求是2的补码),那么这不是问题。在这种情况下,我认为%2
更优雅、更有意义,而&1
是一种 hack,可以想象,有时对于性能来说可能是必要的。例如,我认为 CPython 不会进行这种优化,对于完全解释的语言也是如此(尽管解析开销可能会使两个机器指令之间的差异相形见绌)。不过,如果 C 或 C++ 编译器在可能的情况下没有这样做,我会感到有点惊讶,因为如果之前没有的话,那么在发出指令时这是理所当然的。一般来说,我想说,在 C++ 中,你完全受编译器优化能力的支配。标准容器和算法具有 n 级间接寻址,其中大部分在编译器完成内联和优化后消失。一个像样的 C++ 编译器可以在早餐前处理带有常量值的算术,而一个不像样的 C++ 编译器无论你做什么都会产生垃圾代码。
If you're going to say that some compilers won't optimise
%2
, then you should also note that some compilers use a ones' complement representation for signed integers. In that representation,&1
gives the wrong answer for negative numbers.So what do you want - code which is slow on "some compilers", or code which is wrong on "some compilers"? Not necessarily the same compilers in each case, but both kinds are extremely rare.
Of course if
num
is of an unsigned type, or one of the C99 fixed-width integer types (int8_t
and so on, which are required to be 2's complement), then this isn't an issue. In that case, I consider%2
to be more elegant and meaningful, and&1
to be a hack that might conceivably be necessary sometimes for performance. I think for example that CPython doesn't do this optimisation, and the same will be true of fully interpreted languages (although then the parsing overhead likely dwarfs the difference between the two machine instructions). I'd be a bit surprised to come across a C or C++ compiler that didn't do it where possible, though, because it's a no-brainer at the point of emitting instructions if not before.In general, I would say that in C++ you are completely at the mercy of the compiler's ability to optimise. Standard containers and algorithms have n levels of indirection, most of which disappears when the compiler has finished inlining and optimising. A decent C++ compiler can handle arithmetic with constant values before breakfast, and a non-decent C++ compiler will produce rubbish code no matter what you do.
我定义并使用“IsEven”函数,这样我就不必考虑它,然后我选择一种方法或另一种方法,然后忘记如何检查某些东西是否为偶数。
唯一挑剔/警告的是,我只是说,通过按位运算,您假设了一些关于二进制数字表示的内容,但您不是模数。您正在将数字解释为十进制值。这几乎可以保证适用于整数。然而,考虑到模运算适用于双精度数,但按位运算则不然。
I define and use an "IsEven" function so I don't have to think about it, then I chose one method or the other and forget how I check if something is even.
Only nitpick/caveat is I'd just say that with the bitwise operation, you're assuming something about the representation of the numbers in binary, with modulo you are not. You are interpreting the number as a decimal value. This is pretty much guaranteed to work with integers. However consider that the modulo would work for a double, however the bitwise operation would not.
您关于性能的结论是基于流行的错误前提。
由于某种原因,您坚持将语言操作翻译成“明显的”机器对应项,并根据该翻译得出性能结论。在这种特殊情况下,您得出的结论是,C++ 语言的按位与
&
运算必须通过按位与 机器运算来实现,而模%< /code> 操作必须以某种方式涉及机器除法,据称这会更慢。如果有的话,这种方法的用途也非常有限。
首先,我无法想象现实生活中的 C++ 编译器会以这种“字面”方式解释语言操作,即通过将它们映射到“等效”机器操作。主要是因为人们常常认为等效的机器操作根本不存在。
当涉及到以立即数作为操作数的基本操作时,任何有自尊心的编译器总是会立即“理解”num & 1 和整数
num
的num % 2
执行完全相同的操作,这将使编译器为两个表达式生成完全相同的代码。当然,性能会完全相同。顺便说一句,这不叫“优化”。根据定义,优化是指编译器决定偏离抽象 C++ 机器的标准行为,以生成更高效的代码(保留程序的可观察行为)。在这种情况下没有偏差,这意味着没有优化。
此外,在给定的机器上,实现两者的最佳方式很可能既不是按位与,也不是除法,而是其他一些专用的机器特定指令。最重要的是,很可能根本不需要任何指令,因为特定值的偶数/奇数可能会通过处理器状态标志或类似的东西“免费”公开那。
换句话说,效率论是无效的。
其次,回到最初的问题,确定值的偶数/奇数的更好方法肯定是 num % 2 方法,因为它从字面上实现了所需的检查(“根据定义”),并清楚地表达了检查纯粹是数学的事实。也就是说,它清楚地表明我们关心数字的属性,而不是表示的属性(就像
num & 1<的情况一样) /code> 变体)。
num &当您想要访问数字的值表示形式时,应保留 1
变体。使用此代码进行偶数/奇数检查是一种非常值得怀疑的做法。You conclusion about performance is based on the popular false premise.
For some reason you insist on translating the language operations into their "obvious" machine counterparts and make the performance conclusions based on that translation. In this particular case you concluded that a bitwise-and
&
operation of C++ language must be implemented by a bitwise-and machine operation, while a modulo%
operation must somehow involve machine division, which is allegedly slower. Such approach is of very limited use, if any.Firstly, I can't imagine a real-life C++ compiler that would interpret the language operations in such a "literal" way, i.e. by mapping them into the "equivalent" machine operations. Mostly because more often than one'd think the equivalent machine operations simply do not exist.
When it comes to such basic operations with an immediate constant as an operand, any self-respecting compiler will always immediately "understand" that both
num & 1
andnum % 2
for integralnum
do exactly the same thing, which will make the compiler generate absolutely identical code for both expressions. Naturally, the performance is going to be exactly the same.BTW, this is not called "optimization". Optimization, by definition, is when the compiler decides to deviate from the standard behavior of abstract C++ machine in order to generate the more efficient code (preserving the observable behavior of the program). There's no deviation in this case, meaning that there's no optimization.
Moreover, it is quite possible that on the given machine the most optimal way to implement both is neither bitwise-and nor division, but some other dedicated machine-specific instruction. On top of that, it is quite possible that there won't be any need for any instruction at all, since even-ness/odd-ness of a specific value might be exposed "for free" through the processor status flags or something like that.
In other words, the efficiency argument is invalid.
Secondly, to return to the original question, the more preferable way to determine the even-ness/odd-ness of a value is certainly the
num % 2
approach, since it implements the required check literally ("by definition"), and clearly expresses the fact that the check is purely mathematical. I.e. it makes clear that we care about the property of a number, not about the property of its representation (as would be in case ofnum & 1
variant).The
num & 1
variant should be reserved for situations when you want access to the bits of value representation of a number. Using this code for even-ness/odd-ness check is a highly questionable practice.人们多次提到任何现代编译器都会为这两个选项创建相同的程序集。这让我想起前几天在某处看到的 LLVM 演示页面,所以我想我应该给它走吧。我知道这只不过是轶事,但它确实证实了我们的预期:
x%2
和x&1
的实现相同。我还尝试使用 gcc-4.2.1 (
gcc -S foo.c
) 编译这两个文件,生成的程序集是相同的(并粘贴在这个答案的底部)。对第一个程序进行编程:
结果:
对第二个程序进行编程:
结果:
GCC 输出:
It's been mentioned a number of times that any modern compiler would create the same assembly for both options. This reminded me of the LLVM demo page that I saw somewhere the other day, so I figured I'd give it a go. I know this isn't much more than anecdotal, but it does confirm what we'd expect:
x%2
andx&1
are implemented identically.I also tried compiling both of these with gcc-4.2.1 (
gcc -S foo.c
) and the resultant assembly is identical (and pasted at the bottom of this answer).Program the first:
Result:
Program the second:
Result:
GCC output:
这一切都取决于上下文。如果它是低级别的系统上下文,我实际上更喜欢 &1 方法。在许多此类上下文中,“偶数”对我来说基本上意味着低位为零,而不是可以被二整除。
然而:你的一个衬垫有一个错误。
您不能将
后者
与 x 与 1==0 进行与运算,即它与 x 与 0 进行与运算,得到 0,当然,它总是评估为 false。
因此,如果您完全按照您的建议进行操作,那么所有数字都是奇数!
It all depends on context. I actually prefer the &1 approach myself if it's a low level, system context. In many of these kinds of contexts, "is even" basically means has low bit zero to me, rather than is divisible by two.
HOWEVER: Your one liner has a bug.
You must go
not
The latter ANDs x with 1==0, ie it ANDs x with 0, yielding 0, which always evaluates as false of course.
So if you did it exactly as you suggest, all numbers are odd!
任何现代编译器都会优化模运算,因此速度不是问题。
我想说使用模会让事情更容易理解,但是创建一个使用 x & 的
is_even
函数。 1 方法为您提供两全其美的方法。Any modern compiler will optimise away the modulo operation, so speed is not a concern.
I'd say using modulo would make things easier to understand, but creating an
is_even
function that uses thex & 1
method gives you the best of both worlds.他们都非常直观。
我倾向于
num % 2 == 0
,但我确实没有偏好。当然,就性能而言,这可能是一个微观优化,所以我不会担心它。They're both pretty intuitive.
I'd give a slight edge to
num % 2 == 0
, but I really don't have a preference. Certainly as far as performance goes, it's probably a micro-optimization, so I wouldn't worry about it.我花了几年坚持认为,任何合理的编译器都值得其在磁盘上消耗的空间,将
num % 2 == 0
优化为num & 1 == 0 。然后,通过分析不同原因的反汇编,我有机会实际验证我的假设。
事实证明,我错了。 Microsoft Visual Studio,一直到 2013 版本,都会为
num % 2 == 0
生成以下目标代码:是的,确实如此。这是在发布模式下,启用了所有优化。无论是针对 x86 还是 x64 进行构建,您都会获得几乎相同的结果。你可能不会相信我;我自己都几乎不相信。
它基本上完成了您对 num & 的期望。 1 == 0:
相比之下,GCC(早至 v4.4)和 Clang(早至 v3.2)确实如此人们所期望的,为两个变体生成相同的目标代码。但是,根据 Matt Godbolt 的交互式编译器,ICC 13.0.1 也超出了我的预期。
当然,这些编译器并没有错。这不是一个错误。有很多技术原因(正如其他答案中充分指出的那样)为什么这两个代码片段不相同。这里肯定有一个“过早优化是邪恶的”论点。诚然,我花了很多年才注意到这一点,这是有原因的,即便如此,我也只是错误地偶然发现了它。
但是,就像 Doug T. 所说的,最好在你的代码中定义一个
IsEven
函数。某个地方的库可以正确处理所有这些小细节,这样您就不必再考虑它们,并保持代码的可读性。如果您经常以 MSVC 为目标,也许您会像我一样定义这个函数:I spent years insisting that any reasonable compiler worth the space it consumes on disk would optimize
num % 2 == 0
tonum & 1 == 0
. Then, analyzing disassembly for a different reason, I had a chance to actually verify my assumption.It turns out, I was wrong. Microsoft Visual Studio, all the way up through version 2013, generates the following object code for
num % 2 == 0
:Yes, indeed. This is in Release mode, with all optimizations enabled. You get virtually equivalent results whether building for x86 or x64. You probably won't believe me; I barely believed it myself.
It does essentially what you would expect for
num & 1 == 0
:By way of comparison, GCC (as far back as v4.4) and Clang (as far back as v3.2) do what one would expect, generating identical object code for both variants. However, according to Matt Godbolt's interactive compiler, ICC 13.0.1 also defies my expectations.
Sure, these compilers are not wrong. It's not a bug. There are plenty of technical reasons (as adequately pointed out in the other answers) why these two snippets of code are not identical. And there's certainly a "premature optimization is evil" argument to be made here. Granted, there's a reason it took me years to notice this, and even then I only stumbled onto it by mistake.
But, like Doug T. said, it is probably best to define an
IsEven
function in your library somewhere that gets all of these little details correct so that you never have to think about them again—and keep your code readable. If you regularly target MSVC, perhaps you'll define this function as I've done:这两种方法都不是显而易见的,特别是对于刚接触编程的人来说。您应该使用描述性名称定义一个内联函数。您在其中使用的方法并不重要(微观优化很可能不会以明显的方式使您的程序更快)。
不管怎样,我相信 2) 更快,因为它不需要除法。
Both approaches are not obvious especially to someone who is new to programming. You should define an
inline
function with a descriptive name. The approach you use in it won't matter (micro optimizations most likely won't make your program faster in a noticeable way).Anyway, I believe 2) is much faster as it doesn't require a division.
我不认为模数使事情更具可读性。
两者都有道理,而且两个版本都是正确的。计算机以二进制存储数字,因此您可以只使用二进制版本。编译器可能会用高效版本替换模版本。但这听起来像是更喜欢取模的借口。
在这种非常特殊的情况下,两个版本的可读性是相同的。刚接触编程的读者可能甚至不知道可以使用模 2 来确定数字的偶数性。读者必须自行推论。他可能甚至不知道模运算符!
在推导语句背后的含义时,甚至可以更容易地阅读二进制版本:(
我使用“b”后缀只是为了澄清,它不是 C/C++)
对于模版本,您必须仔细检查如何操作在其详细信息中定义(例如,检查文档以确保
0 % 2
是您所期望的)。二进制
AND
更简单并且没有歧义!只有运算符优先级可能是二元运算符的陷阱。但这不应该成为避免使用它们的理由(总有一天,即使是新程序员也会需要它们)。I don't think the modulo makes things more readable.
Both make sense, and both versions are correct. And computers store numbers in binary, so you can just use the binary version.The compiler may replace the modulo version with an efficient version. But that sounds like an excuse for prefering the modulo.
And readability in this very special case is the same for both versions. A reader that is new to programming may not even know that you can use modulo 2 to determine the even-ness of a number. The reader has to deduce it. He may not even know the modulo operator!
When deducing the meaning behind the statements, it could even be easier to read the binary version:
(I used the "b" suffix for clarification only, its not C/C++)
With the modulo version, you have to double-check how the operation is defined in its details (e.g. check documentation to make sure that
0 % 2
is what you expect).The binary
AND
is simpler and there are no ambiguities!Only the operator precedence may be a pitfall with binary operators. But it should not be a reason to avoid them (some day even new programmers will need them anyway).在这一点上,我可能只是增加了噪音,但就可读性而言,模选项更有意义。如果你的代码不可读,那么它实际上毫无用处。
另外,除非这是在资源确实紧张的系统(我认为是微控制器)上运行的代码,否则不要尝试针对编译器的优化器进行优化。
At this point, I may be just adding to the noise, but as far as readability goes, the modulo option makes more sense. If your code is not readable, it's practically useless.
Also, unless this is code to be run on a system that's really strapped for resources (I'm thinking microcontroller), don't try to optimize for the compiler's optimizer.