如何检查整数是偶数还是奇数?

时间:2020-03-06 15:00:36  来源:igfitidea点击:

如何检查给定数字在C中是偶数还是奇数?

解决方案

我想说的是将其除以2,如果余数为0,则为偶数,否则为奇数。

使用模量(%)可以简化这一过程。

例如。
4%2 = 0因此4是偶数
5%2 = 1因此5为奇数

使用模(%)运算符检查除以2时是否还有余数:

if (x % 2) { /* x is odd */ }

上面有人批评我的回答,指出使用x&1是"更快"或者"更有效"的。我认为情况并非如此。

出于好奇,我创建了两个简单的测试用例程序:

/* modulo.c */
#include <stdio.h>

int main(void)
{
    int x;
    for (x = 0; x < 10; x++)
        if (x % 2)
            printf("%d is odd\n", x);
    return 0;
}

/* and.c */
#include <stdio.h>

int main(void)
{
    int x;
    for (x = 0; x < 10; x++)
        if (x & 1)
            printf("%d is odd\n", x);
    return 0;
}

然后,我用gcc 4.1.3在我的机器之一上编译了5次不同的时间:

  • 没有优化标志。
  • 与-O
  • 与-Os
  • 与-O2
  • 与-O3

我检查了每个编译的汇编输出(使用gcc -S),发现在每种情况下,and.c和modulo.c的输出都是相同的(它们都使用了andl $ 1,%eax指令)。我怀疑这是"新"功能,并且我怀疑它可以追溯到古代版本。我也怀疑任何现代的(过去20年制造的)非arcane编译器,无论是商业的还是开源的,都缺乏这种优化。我会在其他编译器上进行测试,但目前没有任何可用的。

如果其他人愿意测试其他编译器和/或者平台目标并获得不同的结果,我将非常感兴趣。

最后,标准保证了模版本可以工作,无论整数是正数,负数还是零,无论实现方式是有符号整数的表示形式。按位和版本不是。是的,我意识到补码在某种程度上是无处不在的,所以这并不是一个真正的问题。

i % 2 == 0

使用位算术:

if((x & 1) == 0)
    printf("EVEN!\n");
else
    printf("ODD!\n");

这比使用除法或者模量更快。

// C#
bool isEven = ((i % 2) == 0);

如果将数字除以2,则余数为0。如果将数字除以2,则余数为1,则数字为奇数。

// Java
public static boolean isOdd(int num){
    return num % 2 != 0;
}

/* C */
int isOdd(int num){
    return num % 2;
}

方法很棒!

按位方法取决于整数的内部表示。模将在任何有模运算符的地方工作。例如,某些系统实际上使用低级位进行标记(例如动态语言),因此原始的x&1在这种情况下实际上不起作用。

你们太有效率了。我们真正想要的是:

public boolean isOdd(int num) {
  int i = 0;
  boolean odd = false;

  while (i != num) {
    odd = !odd;
    i = i + 1;
  }

  return odd;
}

重复" isEven"。

当然,这不适用于负数。但是光彩照人牺牲了...

解决问题的另一种方法
(欢迎孩子们投票)

bool isEven(unsigned int x)
{
  unsigned int half1 = 0, half2 = 0;
  while (x)
  {
     if (x) { half1++; x--; }
     if (x) { half2++; x--; }

  }
  return half1 == half2;
}

为了回应ffpf,几年前我和一位同事有完全相同的论点,答案是"否",它不适用于负数。

C标准规定负数可以用3种方式表示:

  • 2的补码
  • 1的补码
  • 符号和大小

像这样检查:

isEven = (x & 1);

将适用于2的补码以及符号和幅度表示,但不适用于1的补码。

但是,我认为以下情况适用于所有情况:

isEven = (x & 1) ^ ((-1 & 1) | ((x < 0) ? 0 : 1)));

感谢ffpf指出文本框在我少于字符之后就吃掉了所有东西!

一个不错的是:

/*forward declaration, C compiles in one pass*/
bool isOdd(unsigned int n);

bool isEven(unsigned int n)
{
  if (n == 0) 
    return true ;  // I know 0 is even
  else
    return isOdd(n-1) ; // n is even if n-1 is odd
}

bool isOdd(unsigned int n)
{
  if (n == 0)
    return false ;
  else
    return isEven(n-1) ; // n is odd if n-1 is even
}

请注意,此方法使用包含两个功能的尾部递归。如果编译器像Scheme编译器一样支持尾递归,则可以有效地实现它(变成一个while / until循环)。在这种情况下,堆栈不应溢出!

我知道这只是语法糖,仅适用于.net,但是扩展方法呢...

public static class RudiGroblerExtensions
{
    public static bool IsOdd(this int i)
    {
        return ((i % 2) != 0);
    }
}

现在我们可以执行以下操作

int i = 5;
if (i.IsOdd())
{
    // Do something...
}

[笑话模式="开启"]

public enum Evenness
{
  Unknown = 0,
  Even = 1,
  Odd = 2
}

public static Evenness AnalyzeEvenness(object o)
{

  if (o == null)
    return Evenness.Unknown;

  string foo = o.ToString();

  if (String.IsNullOrEmpty(foo))
    return Evenness.Unknown;

  char bar = foo[foo.Length - 1];

  switch (bar)
  {
     case '0':
     case '2':
     case '4':
     case '6':
     case '8':
       return Evenness.Even;
     case '1':
     case '3':
     case '5':
     case '7':
     case '9':
       return Evenness.Odd;
     default:
       return Evenness.Unknown;
  }
}

[笑话模式="关闭"]

编辑:向枚举添加了令人困惑的值。

IsOdd(int x){返回true; }

正确性证明考虑所有正整数的集合,并假设存在一个非奇数的非空整数集合。因为正整数是有序的,所以会有一个最小的非奇数,它本身是很奇的,因此很明显该数字不能在集合中。因此,此集合不能为非空。对负整数重复此操作,除了查找最大的非奇数。

便携的:

i % 2 ? odd : even;

不可携带:

i & 1 ? odd : even;

i << (BITS_PER_INT - 1) ? odd : even;

为了讨论...

我们只需要查看任何给定数字中的最后一位数字,看看它是偶数还是奇数。
有符号,无符号,肯定,否定在此方面都是相同的。
因此,这应该可以正常工作:-

void tellMeIfItIsAnOddNumberPlease(int iToTest){
  int iLastDigit;
  iLastDigit = iToTest - (iToTest / 10 * 10);
  if (iLastDigit % 2 == 0){
    printf("The number %d is even!\n", iToTest);
  } else {
    printf("The number %d is odd!\n", iToTest);
  }
}

关键在代码的第三行,除法运算符执行整数除法,因此结果缺少结果的小数部分。因此,例如222/10将得出22的结果。然后再乘以10,得到220。从原始222减去该数字,最后得到2,根据魔术,该数字与原始数字的最后一位相同。 ;-)
括号在这里提醒我们计算的顺序。首先进行除法和乘法,然后从原始数中减去结果。我们可以省去它们,因为除法和乘法的优先级比减法的优先级高,但这使我们的代码"更具可读性"。

如果需要,我们可以使所有内容完全不可读。对于现代编译器而言,这没有任何区别:-

printf("%d%s\n",iToTest,0==(iToTest-iToTest/10*10)%2?" is even":" is odd");

但这会使将来的代码难以维护。试想一下,我们想将奇数数字的文本更改为"不是偶数"。然后其他人想要找出我们所做的更改并执行svn diff或者类似的操作...

如果我们不担心可移植性,而是更多地关注速度,则可以查看一下最低有效位。如果该位设置为1,则为奇数;如果为0,则为偶数。
在小端系统上,例如Intel的x86架构,将是这样的:-

if (iToTest & 1) {
  // Even
} else {
  // Odd
}

如果要提高效率,请使用按位运算符(" x&1"),但是如果要提高可读性,请使用模2(" x%2")

在"创意但令人困惑的类别"中,我提供:

int isOdd(int n) { return n ^ n * n ? isOdd(n * n) : n; }

此主题的一个变体,特定于Microsoft C ++:

__declspec(naked) bool __fastcall isOdd(const int x)
{
    __asm
    {
        mov eax,ecx
        mul eax
        mul eax
        mul eax
        mul eax
        mul eax
        mul eax
        ret
    }
}

int isOdd(int i){
  return(i % 2);
}

完毕。

我会建立一个整数的奇偶校验表(如果是奇数则为1,如果是0,则为0)(以便可以进行查找:D),但是gcc不允许我制作这样大小的数组:

typedef unsigned int uint;

char parity_uint [UINT_MAX];
char parity_sint_shifted [((uint) INT_MAX) + ((uint) abs (INT_MIN))];
char* parity_sint = parity_sint_shifted - INT_MIN;

void build_parity_tables () {
    char parity = 0;
    unsigned int ui;
    for (ui = 1; ui <= UINT_MAX; ++ui) {
        parity_uint [ui - 1] = parity;
        parity = !parity;
    }
    parity = 0;
    int si;
    for (si = 1; si <= INT_MAX; ++si) {
        parity_sint [si - 1] = parity;
        parity = !parity;
    }
    parity = 1;
    for (si = -1; si >= INT_MIN; --si) {
        parity_sint [si] = parity;
        parity = !parity;
    }
}

char uparity (unsigned int n) {
    if (n == 0) {
        return 0;
    }
    return parity_uint [n - 1];
}

char sparity (int n) {
    if (n == 0) {
        return 0;
    }
    if (n < 0) {
        ++n;
    }
    return parity_sint [n - 1];
}

因此,让我们改为使用偶数和奇数的数学定义。

即使存在整数k使得n = 2k,整数n也是。

如果存在整数k使得n = 2k + 1,则整数n为奇数。

这是它的代码:

char even (int n) {
    int k;
    for (k = INT_MIN; k <= INT_MAX; ++k) {
        if (n == 2 * k) {
            return 1;
        }
    }
    return 0;
}

char odd (int n) {
    int k;
    for (k = INT_MIN; k <= INT_MAX; ++k) {
        if (n == 2 * k + 1) {
            return 1;
        }
    }
    return 0;
}

让C-整数表示给定C编译中int的可能值。 (请注意,C整数是整数的子集。)

现在,人们可能会担心,对于C整数中的给定n,C整数中可能不存在相应的整数k。但是只要有一点证明,就可以证明对于所有整数n,| n |。 <= | 2n | (*),其中| n |为"如果n为正,则为n,否则为-n"。换句话说,对于所有n个整数,至少具有以下条件之一(实际上是情况(1和2)或者情况(3和4),但我在这里不做证明):

情况1:n <= 2n。

情况2:-n <= -2n。

情况3:-n <= 2n。

情况4:n <= -2n。

现在取2k = n。 (如果n为偶数,则ak确实存在,但我不会在这里证明。如果n不偶数,则even中的循环无论如何都不会提前返回,所以没关系。)但这意味着k < n,如果n不等于(*),并且对于所有m,则整数2m = z表示z(不再次证明),这意味着z在给定m不为0的情况下不等于m。在n为0、2 * 0 = 0,所以甚至完成了0(如果n = 0则0在C整数中,因为n在函数"偶数"中在C整数中,因此k = 0在C整数中)。因此,如果n为偶数,则C整数中的n就存在C整数中的k。

一个类似的论点表明,如果n为奇数,则在C整数中存在一个k,使得n = 2k + 1.

因此,此处介绍的"偶数"和"奇数"功能将对所有C整数均正常工作。