繁体   English   中英

打印数字的二进制表示

[英]print binary representation of a number

我想打印一个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");
}

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

有两种非独家可能性:

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

未定义/实现定义的行为

从未定义的行为开始:首先观察@chux,评估表达式1<<31会在具有32位(或更小) int的系统上产生未定义的行为,例如Windows和Visual Studio的C编译器提供的行为。 两个操作数都是int类型,因此结果是int类型,但是算术上正确的结果超出了可以由该类型表示的值范围。 在这种情况下的行为将为无符号整数结果定义,但对于有符号整数类型(如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)

输出格式

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

机器表示

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

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

但是,您的代码输出的值的逻辑表示不一定符合物理表示。 即使假设您没有遇到各种对象表示的转换或大小等问题,您的代码也假定物理布局是从最重要的字节到最不重要的字节。 也就是说,它打印一个大端表示,而不管实际的物理表示。 在x86和x86_64,原生物理表示int s是 -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<<31移位一位通过值位并可能移位到符号(或填充)位。 这是C中未定义的行为。

n & i试图“和” unsigned int位和signed intsigned 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的补充@John Bollinger

使用负绝对值if (i > 0) i = -i; 因为正绝对值不适用于INT_MIN 2的补码。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM