【问题标题】:Finding whether a signed and an unsigned integer are both even or both odd查找有符号整数和无符号整数是偶数还是奇数
【发布时间】:2017-12-26 14:59:21
【问题描述】:

我有一个int m 和一个unsigned int j,想确定它们是偶数还是奇数。

过去我一直在使用

if((int(j)+m)%2)

捕捉只有一个是奇数的情况。但我担心int 的转换错误地改变了j 的奇偶性。

这些都会遇到问题吗?

if(!(j%2)!=!(m%2))
if(bool(j%2)!=bool(j%2))

我知道

if(j%2!=m%2)

不起作用,因为当m 为负数时,'m%2' 将产生-1,无论j%2 的值是什么,它总是会计算为true

【问题讨论】:

  • 如果您没有大量输入,为什么不使用 abs()?
  • @IgnacioVazquez-Abrams 因为 && 没有正确的行为。我实际上是在尝试创建一个逻辑 XOR,这正是 != 所做的。
  • 仅供参考,“奇偶”的正式术语是“parity”(不要与 parity bits 混淆)。
  • @jwodder:不幸的是,该维基百科文章有两个相互冲突的奇偶性定义。如果数字是奇数,则 1 的补码负数的 LSB 将为 0,而不是 1。
  • @jwodder 这就是为什么我没有说平价。

标签: c++ c++11 c++14


【解决方案1】:

不要使用%。这是一个需要位掩码的问题:

bool same_parity = (i & 0x1) == (j & 0x1);

无论i 的符号如何,这都有效,因为该表达式的结果将始终为01

【讨论】:

  • & 是用无符号整数“位”还是 实际 位定义的?在 1s 补码系统中,-1 的低位实际上是0。 1s 恭维系统非常模糊,所以我不知道& 是如何定义的。
  • @Yakk 实际位数。我想这只适用于 2s 补码,但我以前从未在 1s 补码系统上工作过,所以我只是假装它们不存在。
  • @JohnathanGross 你在问题中用% 自己解释了这个问题——它没有你想要的语义。你想要一个f,这样f(x) 给你0 或1,但(%2) 不给你0 或1……它给你0 或+/- 1。
  • @Yakk 我已经抛出了一个 static_assert ,因此编译器可以捕获任何不使用 2 补码的系统。我的计算机使用 2 的补码,我怀疑此代码将永远用于其他任何地方。
  • @JohnathanGross:或者您可以使用& 1u,或者SolutionMill 的变体,然后代码也适用于1 的补码。
【解决方案2】:
if (1 & (i ^ j))
{
// Getting here if i is even and j is odd
// or if i is odd and j is even
}

^exclusive-or 位运算符,它检查两个数字中的每个位是否具有相同的值。例如,如果i 的二进制表示是0101 并且j1100,那么i ^ j 将计算为1001,因为它们的第一位和最后一位不同,而中间位是一样。

&and 位运算符,如果它们都是1,它将检查两个数字中的每个位。

因为只有每个数字的最后一位决定它是偶数还是奇数,i ^ j 将评估为...xxx0,如果它们都是偶数或奇数,...xxx1 否则(xs 无关紧要,无论如何我们都不在看它们)。由于1 实际上是...0001,如果ij 都是偶数或奇数,则1 & (i ^ j) 的计算结果为0,否则1

这适用于无符号数、2s 补码和符号和大小的任意组合,但如果恰好一个为负数,则不适用于罕见的 1s 补码。

【讨论】:

  • 啊,你秒杀我……这个生成最短的 x86 程序集。
  • 这是最简单、最有效的方法。
  • 关于这在 1s 补码系统上不起作用的断言(可能)是不正确的。操作i ^ j 在 C++ 标准中定义为在执行按位 XOR 之前执行“[t]he 通常的算术转换”(在 N3337 草案中,请参见 p.117 S5.12)。除非有符号类型能够表示无符号类型的所有可能值,否则这意味着有符号值将转换为无符号类型(第 84 页,S5 第 9 段)。将有符号数转换为无符号数的规则(第 80 页,S4.7 第 2 段)要求结果“与源整数一致(模 2^n ...
  • ... 其中 n 是用于表示无符号类型的位数)”。这个congruence relationship 将保留值的奇偶性,因此给出正确的结果。同样不能但是,如果两个值都存储在 signed 类型中,如上面的@CoDEmanX 所示。
  • @MaximEgorushkin 您可以将其转换为无符号但溢出不是问题。
【解决方案3】:

将两个整数相加会增加它们的奇偶性,因此解决方案很简单:

if ( (j + m) % 2 )

无符号环绕不会干扰此属性,因为它是以偶数 UINT_MAX+1 为模完成的。

此解决方案不依赖于任何特定于实现的细节,例如负数表示。


脚注:我很难理解为什么这么多其他答案被确定为使位移、位补码、XOR 等问题复杂化。不幸的是,IMO,它有时在 C 或 C++ 中得到了美化社区来编写棘手的代码而不是简单的代码。

【讨论】:

  • 这段代码依赖于“通常的算术转换”来做正确的事情,所以这是否有效,以及如果类型发生轻微变化是否会保持有效,对于对于其他一些答案,每个人都是如此。
  • @JeremyR 如果您发现它更具可读性,您可以使用 Yakk 的显式转换建议。 每个代码都依赖于语言规则!
  • 这会为 x86 (godbolt.org/g/BC6EFh) 生成 asm,它至少与其他选项一样好,并且在某些情况下更好(尤其是在以后需要 jm 的任何时候),因为它可以使用lea 作为非破坏性添加。它在 ARM 或 MIPS 等其他 ISA 上应该大致相同。使用+ 仅在您指出时才明显。有趣的事实:编译器(ICC 除外)可以在 1 & (i ^ j)(i & 0x1) != (j & 0x1) 之间转换,但不能从其中任何一个转换到这个。
  • (j + m + 1) % 2 为您提供相反的条件,并且仍然可以使用 x86 lea eax, [rsi+rdi+1] 指令计算。 (加上and eax,1 或测试/jcc 或其他)。当实际在寄存器中创建 bool 而不是在其上进行分支时,其他答案最终以 not 指令结束。
  • 我不同意这里自我夸大的脚注。屏蔽最后一位以检查奇偶校验并不复杂,其好处是它适用于任何输入。 (j+m)%2 如果两者都是其和溢出的有符号整数类型,则未定义。
【解决方案4】:

将大于INT_MAXunsigned int 转换为int 不能保证返回合理的值。结果未定义。

int 转换为unsigned int 始终会导致定义的行为——它对某些k 进行数学运算mod 2^k 足够大,以至于每个正数int 都小于2^k

if((int(j)+m)%2)

应该是

if((j+unsigned(m))%2)

改为。

if((j%2)==(unsigned(m)%2))

是查看两者是否具有相同奇偶性的最简单方法。移动到 unsigned aka mod 2^k 将保持奇偶校验,并且在 unsigned %2 中正确返回奇偶校验(而不是负奇偶校验)。

【讨论】:

  • 只要结果被正确表达,一个好的编译器应该可以生成最优的代码,不管你写什么,可读性是一个品味问题(我不会多余的括号参数==,例如,但我会写 ...%2!=0 而不是依赖隐式转换为bool)。但是,我认为只有一个 % 的解决方案更可取,就像您的第一个解决方案一样。另请注意,可以只写if ((j+m)%2!=0),因为有符号的m 会自动转换为另一个操作数j 的无符号类型,但显式转换确实使这一点更加明确。
  • 您在第一段中描述的转换结果是实现定义的,而不是未定义的。 (在 C 和 C++ 中)
  • 您可能想看看std::make_unsigned 以获得最大的通用性。
【解决方案5】:

别太聪明

这些都会遇到问题吗?

if(!(j%2)!=!(m%2))
if(bool(j%2)!=bool(j%2))

我看到的一个问题是可读性。对于其他人(或未来的自己)来说,它应该做什么或实际做什么可能并不明显。

花一些额外的台词可以让你更有表现力:

#include <cmath>

const bool fooIsEven = foo % 2 == 0;
const bool barIsEven = std::abs(bar) % 2 == 0;
if (fooIsEven == barIsEven)
{
  // ...
}

还可以考虑实现一个正确命名的函数,该函数提供两个给定整数类型的奇偶校验比较。这不仅可以清理您的代码,还可以防止您重复自己。

编辑:通过调用 std::abs 替换强制转换

【讨论】:

  • 什么是std::abs(INT_MIN)?演员表的优点是它确实有效。
  • @Ben Voigt - 好点。感谢您指出。根据 cppreference.com:“在 2 的补码系统中,最负值的绝对值超出范围,例如对于 32 位 2 的补码类型 int,INT_MIN 为 -2147483648,但可能的结果 2147483648 更大大于 INT_MAX,即 2147483647。”
  • 这是% 为有符号整数生成可怕代码的情况之一,因为符号保留在结果中:godbolt.org/g/YSSARf
  • @MaximEgorushkin 对不起,有符号整数的溢出并不能保证符号被保留,除非您指定编译器并提供它提供的保证;结果通常是未定义的行为。
  • 为什么需要std::abs(bar) 才能进行模组化?如果您检查的是 0,那么 bar 的原始符号应该无关紧要,不是吗?
【解决方案6】:

这可以简化:

if(!(j%2)!=!(m%2))
if(bool(j%2)!=bool(j%2))

到:

if ((abs(m) % 2) != (j % 2))

一定要包含 math.h

#include <math.h>

绝对值会带走存储中最左边的符号位。

将有符号转换为无符号是可以的,并且在 C99 中定义。

找到这个resource

位运算符也应该与 C99 编译器一起使用,具有较小最大值的有符号将转换为较大(有符号到无符号)。

【讨论】:

  • C++ 方式是使用&lt;cmath&gt;(或在本例中为&lt;cstdlib&gt;)库而不是&lt;math.h&gt;
  • abs(int) 返回一个int。允许没有正面表示的负面ints。 int 的溢出是未定义的行为。你能记录下INT_MIN 上的abs(int) 是定义的行为并产生我们想要的结果吗?
  • 因为这是一个 C++ 答案,所以只包含&lt;math.h&gt;。你只会得到absdouble abs(double) 重载。无论如何,因为事实证明有更好的答案是便携安全的,abs 是解决这个问题的糟糕解决方案(出于性能原因,如果没有别的)。
猜你喜欢
  • 2015-03-03
  • 1970-01-01
  • 2015-04-22
  • 1970-01-01
  • 2013-10-02
  • 2017-09-09
  • 1970-01-01
  • 2011-04-30
  • 2010-09-09
相关资源
最近更新 更多