Bat*_*eba 86

只要不对程序引入任何副作用,编译器就可以进行任何喜欢的优化.在你的情况下它不能取消'2',因为表达式将具有不同的奇数值.

x / 2 * 2严格评估为(x / 2) * 2,x / 2如果x是整数类型,则以整数运算执行.

事实上,这是一种惯用的舍入技术.

  • @MSalters:或者,在unsigned`(x&0xfffffffe)`:)的情况下 (31认同)
  • 当然,编译器_is_允许正确地优化它,例如通过用`(x >> 1)<< 1`替换它.IIRC,即使在技术上关闭优化,也有编译器会这样做. (29认同)
  • 我喜欢关于这是惯用语的说明.它也比面具IMO更直观. (4认同)
  • @Voo:为什么?这是一个无符号整数.根据定义,行为是相同的. (3认同)
  • @MSalters:是的,这是一个愚蠢的说法.我修改了. (2认同)
  • @MSalters虽然编译器可以做到这一点,但我们不允许人类这样做,因为它会调用未定义的行为.可能值得一提. (2认同)

Sto*_*ica 53

由于您指定的整数是无符号的,因此您可以使用简单的掩码:

x & (~1u)
Run Code Online (Sandbox Code Playgroud)

这将LSB设置为零,从而产生不大于的立即偶数x.也就是说,如果x有一个不宽于的类型unsigned int.

您当然可以强制它1与更宽的类型相同x,如下所示:

x & ~((x & 1u) | 1u)
Run Code Online (Sandbox Code Playgroud)

但在这一点上,你真的应该把这种方法视为混淆练习,并接受芭丝谢芭的回答.


我当然忘记了标准库.如果你包括stdint.h(或者cstdint,你应该在C++代码中).您可以让实现处理细节:

uintmax_t const lsb = 1;
x & ~lsb;
Run Code Online (Sandbox Code Playgroud)

要么

x & ~UINTMAX_C(1)
Run Code Online (Sandbox Code Playgroud)

  • 如果`x`的类型大于`unsigned int`,我担心`x&(~1u)`不起作用.相反,`x&~1`的行为与所有类型的预期相同.这是一个反直觉的陷阱.如果你坚持使用无符号常量,你必须编写`x&〜(uintmax_t)1`,因为即使`x&~1ULL`也会失败,如果`x`的类型大于`unsigned long long`.更糟糕的是,许多平台现在具有比`uintmax_t`更大的整数类型,例如`__uint128_t`. (4认同)
  • 你不需要`x&(~static_cast <decltype(x)>(1))`或者我需要一杯咖啡吗? (3认同)
  • @Bathsheba - 这绝对是我需要咖啡的人.嗯......虽然关于使用任何无符号整数类型,但我没有给它太多. (2认同)

MSa*_*ers 36

C和C++通常在优化中使用"as if"规则.计算结果必须好像编译器没有优化代码.

在这种情况下,9/2*2=8.编译器可以使用任何方法来实现结果8.这包括位掩码,位移或任何产生相同结果的特定于CPU的hack(x86有很多技巧依赖于它不区分指针的事实)和C和C++不同的整数.

  • [案例和观点](https://godbolt.org/g/CR4q3v).这就是GCC用`-O1`生成的.所有方法都被提炼为相同的机器代码. (11认同)
  • GCC非常不寻常,因为即使禁用了优化,它也会应用这些数学/位错误的窥孔优化.这是一个有趣而值得注意的设计怪癖.其他编译器在禁用优化的情况下将C代码更直接地转换为机器指令,但是一旦启用优化器,输出就完全相同,这就是为什么你应该编写代码以便于阅读,除非你确定你的编译器已经死了. (7认同)

chq*_*lie 21

您可以编写x / 2 * 2,如果有无x符号类型,编译器将生成非常有效的代码以清除最低有效位.

相反,你可以写:

x = x & ~1;
Run Code Online (Sandbox Code Playgroud)

或者可能不太可读:

x = x & -2;
Run Code Online (Sandbox Code Playgroud)

甚至

x = (x >> 1) << 1;
Run Code Online (Sandbox Code Playgroud)

或者这个:

x = x - (x & 1);
Run Code Online (Sandbox Code Playgroud)

或者最后一个,由supercat建议,适用于所有整数类型和表示的正值:

x = (x | 1) ^ 1;
Run Code Online (Sandbox Code Playgroud)

所有上述提议都适用于2的补码架构上的所有无符号整数类型.编译器是否会产生最佳代码是配置和实现质量的问题.

但请注意,x & (~1u)如果类型x大于,则不起作用unsigned int.这是一个反直觉的陷阱.如果你坚持使用无符号常量,那么如果类型大于,则必须写x & ~(uintmax_t)1为偶数.更糟糕的是,许多平台现在有整型大于等.x & ~1ULLxunsigned long longuintmax_t__uint128_t

这是一个小基准:

typedef unsigned int T;

T test1(T x) {
    return x / 2 * 2;
}

T test2(T x) {
    return x & ~1;
}

T test3(T x) {
    return x & -2;
}

T test4(T x) {
    return (x >> 1) << 1;
}

T test5(T x) {
    return x - (x & 1);
}

T test6(T x) {  // suggested by supercat
    return (x | 1) ^ 1;
}

T test7(T x) {  // suggested by Mehrdad
    return ~(~x | 1);
}

T test1u(T x) {
    return x & ~1u;
}
Run Code Online (Sandbox Code Playgroud)

正如Ruslan所建议的那样,在Godbolt的编译器资源管理器进行的测试表明,对于所有上述替代方案都会gcc -O1产生相同的精确代码unsigned int,但更改类型Tunsigned long long显示不同的代码test1u.


Jen*_*edt 7

如果你的值是你所说的任何无符号类型,最简单的是

x & -2;
Run Code Online (Sandbox Code Playgroud)

无符号算术的奇迹使它-2转换为类型x,并且具有包含所有1的位模式,但是对于最低有效位是0.

与其他一些提议的解决方案相反,这应该适用于任何至少与其一样宽的无符号整数类型unsigned.(无论如何,你不应该用较窄的类型进行算术运算.)

额外奖励,如supercat所述,它仅使用有符号类型转换为无符号类型.标准将其定义为模运算.所以结果是始终UTYPE_MAX-1UTYPE无符号的类型的x.特别是,它独立于签名类型的平台的符号表示.

  • 值得注意的是,只是为了避免任何混淆,将-2转换为"无符号"将产生"无符号值",当加到2时,无论系统是否使用二进制补码,都会产生0,**.相比之下,如果一个人具有一个补码系统,则~1将等于-1,当转换为无符号时,将产生一个设置了所有位的值. (4认同)

Art*_*cca 6

到目前为止,我很惊讶的一个选项是使用模运算符.我认为这至少和你原来的片段一样代表你的意图,甚至可能更好.

x = x - x % 2
Run Code Online (Sandbox Code Playgroud)

正如其他人所说的那样,编译器的优化器会等同地处理任何合理的表达式,所以要担心什么更清楚,而不是你认为最快.所有的位调整答案都很有趣,但你绝对不应该使用它们来代替算术运算符(假设动机是算术而不是位调整).