thambi03
thambi03

Reputation: 71

print binary representation of a number

I want to print the binary representation of an int. My solution seems to work for both int and unsigned int in Visual Studio, but somebody told me it's wrong. Does anyone see an error? If so, why does my program seem to work for me?

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");
}

Upvotes: 6

Views: 2232

Answers (2)

John Bollinger
John Bollinger

Reputation: 181734

why does my program seem to work for me?

There are two non-exclusive possibilities:

  1. Your program works correctly for all inputs and conditions you tested, but there are inputs and/or conditions you did not test, on which it fails. As a special case of this, the complaint may be that your program relies on undefined, implementation-defined, or unspecified behavior (which it does), making it inherently wrong even if it happens to work as you expect in your test environment.
  2. You are mistaken about your program working correctly, presumably as a result of a misunderstanding about the required output.

Undefined / implementation-defined behavior

Starting with the undefined behavior: as @chux observed first, evaluating the expression 1<<31 produces undefined behavior on systems featuring 32-bit (or smaller) ints, such as that provided by Windows and Visual Studio's C compiler. Both operands are of type int, therefore the result is of type int, but the arithmetically correct result is outside the range of values that can be represented by that type. Behavior in that case would be defined for an unsigned integer result, but it is explicitly undefined for signed integer types such as int. Since you assign the result to a variable of type unsigned int, you could fix that issue simply by changing the expression to 1u<<31.

Furthermore, the number of bits in any type's representation is unspecified, but your code assumes 32-bit unsigned int. That is indeed the size of the unsigned ints provided by Visual Studio's C compiler, but you don't need to depend on that. You will get the correct implementation-dependent result for every environment by computing the number of bits in the representation of an unsigned int as CHAR_BIT * sizeof(unsigned int).

As long as we're talking about the implementation dependencies, however, it is not necessarily the case that all bits in the representation of an object contribute to its value. There can be padding bits, too, and on an implementation having fewer than 32 value bits in its representation of type unsigned int, the expression 1u << 31 or an equivalent evaluates to zero. To be completely correct, computation of the number of value bits in the representation of unsigned int must be based on the value of UINT_MAX. An alternative expression for the bitmask you create that sidesteps this issue would be ~(UINT_MAX >> 1).

Output format

As to output format, it is unclear what "the" binary form of an int would be, especially given that you want to provide for both negative and positive values. If you are supposed to provide a form for negative values without using a - sign, as your code attempts to do, then either the details of the desired output form must be specified or assumed (e.g. big-endian, 32-bit two's complement), or else you are intended to probe the machine-specific representation of the input values. Since you didn't specify a particular format, if (part of) the problem lies in the output format then I can only conclude that either machine-specific representation or sign/magnitude is desired.

Machine Representation

If the objective is to probe the machine representation of int values, then your program is incorrect on at least two (additional) counts.

First, evaluating the expression n&i involves converting the value of i from type int to type unsigned int. What you therefore print is a representation of the converted value, which is not guaranteed to be the same as the representation of the original int value. In practice, though, it's unlikely that you will ever run into a machine and C implementation where there is an actual difference. Certainly Visual Studio on Windows is not such an environment.

Furthermore, however, your code outputs a logical representation of the value that does not necessarily conform to the physical representation. Even assuming that you do not run afoul of problems with the conversions or with the sizes etc. of various object representations, your code assumes that the physical layout is from most-significant to least-significant byte. That is, it prints a big-endian representation, regardless of the actual physical representation. On x86 and x86_64, the native physical representation of ints is little-endian, and my code below to print the machine representation will print different results than your code does.

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');
}

The standard allows conversions between different pointer types, and it specifically holds that the conversion in that program will result in p being initialized to point to the first byte in the representation of n. The program steps through each byte in the representation (the total number of which being determined via the sizeof operator) and prints the bits in each one, from most-significant to least-significant, similarly to your version. If there are padding bits, they are included.

Sign/magnitude representation

If, on the other hand, you want a signed binary digit string, from most-significant non-zero bit to least-significant bit, then you could do it this way:

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');
}

That works without any assumptions about the representation of values of type int that are not backed by the C standard. Note in particular the special handling when n has the value INT_MIN -- it's messy, but it's necessary because evaluating the expression -INT_MIN can (and on x86 does) produce undefined behavior.

Upvotes: 2

chux
chux

Reputation: 154322

1<<31 shifts a bit passed the value bits and potentially into a sign (or padding) bit. This is undefined behavior in C.

n & i is attempting to "and" bits of an unsigned int and the sign of a signed int.

OP's use of 32 assumes an int is 32-bits wide.

Following is an example the prints the sign and the variable number of bits - works [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);
}

[Edit] Cope with 1's complement @John Bollinger

Using the negative absolute value with if (i > 0) i = -i; as the positive absolute value does not work well with INT_MIN 2's complement.

Upvotes: 2

Related Questions