打印数字的二进制表示

时间:2015-04-21 20:30:58

标签: c bit-manipulation bit

我想打印int的二进制表示。我的解决方案似乎适用于Visual Studio中的intunsigned int,但有人告诉我这是错误的。有人看到错误吗?如果是这样,为什么我的程序似乎对我有用?

void printbin(int n)
{
    unsigned int i = 1<<31;

    for (int j=0; j<32; j++)
    {
        if ((n & i) != 0)
            printf("1");
        else
            printf("0");
        i = i>>1;
    }

    printf("\n");
}

2 个答案:

答案 0 :(得分:2)

  

为什么我的程序似乎对我有用?

有两种非排他性的可能性:

  1. 您的程序对您测试的所有输入和条件都能正常工作,但是有些输入和/或条件没有测试,它们会失败。作为一个特殊情况,抱怨可能是您的程序依赖于未定义的,实现定义的或未指定的行为(它确实如此),即使它恰好在您的测试环境中按预期工作,也会使其本身出错。 / LI>
  2. 你错误地认为你的程序正常工作,可能是由于对所需输出的误解造成的。
  3. 未定义/实现定义的行为

    从未定义的行为开始:首先观察@chux,评估表达式1<<31会在具有32位(或更小)int的系统上产生未定义的行为,例如Windows和Visual Studio的C编译器。两个操作数都是int类型,因此结果是int类型,但是算术上正确的结果超出了可以由该类型表示的值范围。在这种情况下的行为将为 unsigned 整数结果定义,但对于有符号整数类型(例如int)显式未定义。由于您将结果分配给unsigned int类型的变量,因此只需将表达式更改为1u<<31即可解决该问题。

    此外,未指定任何类型表示中的位数,但您的代码假定为32位unsigned int。这确实是Visual Studio C编译器提供的unsigned int的大小,但您不需要依赖它。通过将unsigned int的表示中的位数计算为CHAR_BIT * sizeof(unsigned int),您将获得针对每个环境的正确的依赖于实现的结果。

    然而,只要我们讨论实现依赖性,就不一定是对象表示中的所有位都有助于其值的情况。也可以有填充位,并且在类型unsigned int的表示中具有少于32个位的实现上,表达式1u << 31或等效值计算为零。为完全正确,unsigned int表示中位数的计算必须基于UINT_MAX的值。您创建的用于回避此问题的位掩码的替代表达式为~(UINT_MAX >> 1)

    输出格式

    至于输出格式,目前还不清楚&#34;&#34;&#34; int的二进制形式,特别是考虑到你想要提供负值和正值。如果您应该在不使用-符号的情况下提供负值的表单,就像您的代码尝试那样,那么必须指定或假设所需输出表单的详细信息(例如big-endian,32-第二位的补充),否则你打算探测输入值的机器特定表示。由于您没有指定特定格式,如果(部分)问题出现在输出格式中,那么我只能得出结论是需要特定于机器的表示或符号/幅度。

    机器表示

    如果目标是探测int值的机器表示,那么您的程序在至少两个(额外)计数上是不正确的。

    首先,评估表达式n&i涉及将i的值从类型int转换为类型unsigned int。因此,您打印的是转换的值的表示,该值不保证与原始int值的表示相同。但实际上,你不太可能遇到存在实际差异的机器和C实现。当然,Windows上的Visual Studio不是这样的环境。

    此外,您的代码输出的值的逻辑表示不一定符合物理表示。即使假设您没有遇到各种对象表示的转换或大小等问题,您的代码也假定物理布局是从最重要的字节到最不重要的字节。也就是说,它打印一个大端表示,而不管实际的物理表示。在x86和x86_64上,int的本机物理表示是 little -endian,下面打印机器表示的代码将打印出与您的代码不同的结果。

    void printbin(int n)
    {
        unsigned char *p = (unsigned char *) &n;
    
        for (int j=0; j < sizeof(n); j++)
        {
            for (unsigned char mask = 1u << (CHAR_BIT - 1); mask; mask >>= 1) {
                putchar((*p & mask) ? '1' : '0');
            }
            p += 1;
        }
    
        putchar('\n');
    }
    

    该标准允许不同指针类型之间的转换,并且它特别认为该程序中的转换将导致p被初始化为指向n表示中的第一个字节。程序逐步执行表示中的每个字节(通过sizeof运算符确定的总数)并打印每个字节中的位,从最重要到最不重要,与您的版本类似。如果有填充位,则包括它们。

    符号/幅度表示

    另一方面,如果您想要一个带符号的二进制数字字符串,从最重要的非零位到最低有效位,那么您可以这样做:

    void printbin_digits(unsigned int n) {
        char bits[CHAR_BIT * sizeof(unsigned int)] = {0};
        int bit_count = 0;
    
        while (n) {
            bits[bit_count++] = n % 2;
            n >>= 1;
        }
        while (bit_count) {
            putchar(bits[--bit_count] ? '1' : 0);
        }
    }
    
    void printbin(int n)
    {
        if (n == 0) {
            putchar('0');
        } else if (n == INT_MIN) {
            putchar('-');
            printbin_digits(-(n / 2));
            putchar((n % 2) ? '1' : '0');
        } else if (n < 0) {
            putchar('-');
            printbin_digits(-n);
        } else {
            printbin_digits(n);
        }
    
        putchar('\n');
    }
    

    对于没有C标准支持的类型int的值的表示没有任何假设。请特别注意当n具有值INT_MIN时的特殊处理 - 它很麻烦,但它是必要的,因为评估表达式-INT_MIN可以(并且在x86上)确实产生了未定义的行为。

答案 1 :(得分:2)

1<<31移位一位通过值位并可能移位到符号(或填充)位。这是C中未定义的行为。

n & i正在尝试&#34;和&#34; unsigned int的位和signed int的符号。

OP使用32假设int为32位宽。

以下是打印符号和可变位数的示例 - 工作[INT_MIN...INT_MAX]

#include <limits.h>
void printbin_c(int n) {
  char buf[CHAR_BIT * sizeof n + 1];
  char *p = &buf[sizeof buf - 1];
  *p = '\0';

  int i = n;
  if (i > 0) {
    i = -i;
  }

  do {
    p--;
    *p = '0' - i%2;
    i /= 2;
  } while (i);

  if (n < 0) {
    p--;
    *p = '-';
  }

  puts(p);
}

[编辑]应对1&#39补; @John Bollinger

使用带有if (i > 0) i = -i;的负绝对值作为正绝对值与INT_MIN 2补码不兼容。