使整数均匀

Joã*_*ela 13 c c++ integer bit-manipulation

有时我需要确定某个整数是偶数.因此我可以使用以下代码:

int number = /* magic initialization here */;

// make sure the number is even
if ( number % 2 != 0 ) {
    number--;
}
Run Code Online (Sandbox Code Playgroud)

但这似乎并没有被非常有效的最有效的方式做到这一点,所以我可以做到以下几点:

int number = /* magic initialization here */;

// make sure the number is even
number &= ~1;
Run Code Online (Sandbox Code Playgroud)

但(除了不可读)我不确定该解决方案是否完全可移植.

  • 您认为哪种解决方案最好?
  • 第二种解决方案是否完全便携?
  • 第二种解决方案比第一种解决方案快得多吗?
  • 您对此问题还有哪些其他解决方案?
  • 如果我在内联方法中执行此操作会怎么样?它(理论上)应该与这些解决方案一样快,可读性不应再成为问题,这是否会使第二种解决方案更可行?

注意:此代码应该只适用于正整数,但有一个解决方案也适用于负数将是一个加号.

sus*_*att 31

就个人而言,我会使用内联辅助函数.

inline int make_even(int n)
{
    return n - n % 2;
}

// ....

int m = make_even(n);
Run Code Online (Sandbox Code Playgroud)

  • 返回n - (n%2); 永远不要依赖运营商优先权. (9认同)
  • @Catcall:当然,我理解这种情绪,但是我也认为基本算术运算符的优先级已经很明显,没有括号,没有人需要查找它们.仍然,指出做得好. (5认同)
  • @suszterpatt:因为比我更优秀的程序员说这是一个坏主意,而我的经验表明他们是对的."除非读者和编译者都理解作者,否则该程序无法正常通信." /编程风格的元素/,Kernighan和Plauger,第15页."不要只是做对 - 显然是对的,所以没有人必须查阅它." /编写固体代码/,Maguire,第139页.你会在McConnell/Code Complete /,Eckel/Thinking in Java /等中找到相同的想法.我不认为"从不"太强大; 短表达的坏习惯导致长期表达的坏习惯. (4认同)
  • 为清晰起见+1.无论如何,这段代码可能会编译成`m = n&~1`或类似的代码. (3认同)
  • @Catcall:为什么不呢? (3认同)

JSB*_*ոգչ 6

我会使用第二种解决方案.在任何二进制表示中,无论位数,big-endian与little-endian或其他架构差异如何,该操作都将具有将最低位设置为零的效果.它快速且完全便携.如果遇到任何无法弄清楚它意味着什么的可怜C程序员,可以通过评论来解释代码的意图.

  • 当你说"完全可移植"时,可能你的意思是"对任何你可能会使用的实现".它不能完全移植到C和C++标准允许的任何实现,因为1s'补码表示给出负奇数,最后一位为0. (5认同)

Tom*_*mek 6

int even_number = (number / 2) * 2;
Run Code Online (Sandbox Code Playgroud)

只要优化器不妨碍(不应该但是谁知道),这应该适用于任何架构.

  • @Tomek/@ Steve没有编译器可以进行你描述的优化!整数除法*定义*以截断余数. (10认同)
  • 史蒂夫:一个*破*编译器可以:-) (4认同)

Joã*_*ela 5

在接受答案之前,我将做自己的尝试,以总结并完成此处找到的一些信息:

描述了四种可能的方法(以及其中的一些细微变化)。

  1. if (number % 2 != 0) { number--; }
  2. number&= ~1
  3. number = number - (number % 2);
  4. number = (number / 2) * 2;

在继续进行任何操作之前,让我先澄清一下:即使可以证明一种方法比另一种方法快200%,最坏的方法也是如此之快,以至于只有这种方法才能获得可见的增益,但是使用这些方法中的任何一种方法的预期收益都是最小的。如果在受CPU约束的应用程序中多次调用此方法,则速度最快。因此,这比真正的优化更有趣。

分析

可读性

就可读性而言,我将方法1评为最易读,将 方法4评为次优,将方法2评为最差。人们可以自由地不同意,但我之所以对他们进行排名是因为:

  1. 方法1中,尽可能明确的是,如果数字为奇数,则要从中减去,使之为偶数。
  2. 方法4也是非常明确的,但是我将其排在第二位,因为乍一看您可能会认为它什么也没做,而后者只有一小部分,就像“哦...整数除法”。
  3. 就可读性而言,方法2和3几乎等效,但是许多人不习惯按位操作,因此我将方法2评为更差。

考虑到这一点,我要补充一点,我通常认为,实现此目标的最佳方法是使用inline函数,并且没有一个选择是不可读,可读性不是真正的问题(在代码中的直接使用是明确且清晰的,阅读方法永远不会那么难)。

如果您不想使用inline方法,我建议您仅使用 方法1方法4

相容性问题

下溢

已经提到,方法1可能会下溢,具体取决于处理器表示整数的方式。只是为了确保您可以STATIC_ASSERT在使用方法1时添加以下内容 。

STATIC_ASSERT(INT_MIN % 2 == 0, make_even_may_underflow);
Run Code Online (Sandbox Code Playgroud)

对于方法3INT_MIN取决于结果是否具有除数或除数相同的符号,即使在甚至不是时也可能不会下溢。具有相同除数符号的永远不会下溢,因为 INT_MIN - (-1)它接近于0。请添加以下内容STATIC_ASSERT以确保:

STATIC_ASSERT(INT_MIN % 2 == 0 || -1 % 2 < 0, make_even_may_underflow);
Run Code Online (Sandbox Code Playgroud)

当然,如果STATIC_ASSERT失败,您仍然可以使用这些方法,因为只有将INT_MIN传递给您的make_even方法时这才是问题,但是我强烈建议您这样做。

(Un)支持的位表示

使用方法2时,应确保编译器的位表示形式符合预期:

STATIC_ASSERT( (1 & ~1) == 0, unsupported_bit_representation);

// two's complement OR sign-and-magnitude.
STATIC_ASSERT( (-3 & ~1) == -4 || (-3 & ~1) == -2 , unsupported_bit_representation); 
Run Code Online (Sandbox Code Playgroud)

速度

我还使用Unix time实用程序进行了一些幼稚的速度测试。我对每种不同的方法(及其变体)运行了4次并记录了结果,因为结果变化不大,所以我认为没有必要运行更多的测试。

所获得的结果表明方法4方法2是所有方法中最快的。

结论

根据提供的信息,我建议使用方法4。它可读性强,我不知道任何兼容性问题,并且表现出色。

我希望您喜欢这个答案,并使用此处包含的信息做出自己明智的选择。:)


源代码,如果你想检查我的结果是可用的。请注意,这些测试是使用g++Mac OS X 编译并在Mac OS X上运行的。不同的平台和编译器可能会给出不同的结果。