Jump to: navigation, search

The C Programming Language, 2nd Edition, by Kernighan and Ritchie
Exercise 3.04 on page 64

In a two's complement number representation, our version of itoa does not handle the largest negative number, that is, the value of n equal to -(2 to the power (wordsize - 1)) . Explain why not. Modify it to print that value correctly regardless of the machine on which it runs.




Solution by Paul Griffiths

Note: error spotted by Wayne Lubin and fixed. Wayne Lubin's query involved Paul's discussion of two's complement. The text has now been corrected (by Paul).

Exercise 3-4 explanation: There are a number of ways of representing signed integers in binary, for example, signed-magnitude, excess-M, one's complement and two's complement. We shall restrict our discussion to the latter two. In a one's complement number representation, the binary represenation of a negative number is simply the binary representation of its positive counterpart, with the sign of all the bits switched. For instance, with 8 bit variables:

                SIGNED  BINARY  UNSIGNED
                
                  25   00011001     25
                 -25   11100110    230

                 127   01111111    127
                -127   10000000    128


The implications of this are (amongst others) that there are two ways of representing zero (all zero bits, and all one bits), that the maximum range for a signed 8-bit number is -127 to 127, and that negative numbers are biased by (2^n - 1) (i.e. -I is represented by (2^n - 1) - (+I). In our example, so:

                Bias = 2^8 - 1 = 255 = 11111111
                Subtract 25          = 00011001
                Equals               = 11100110


In a two's complement representation, negative numbers are biased by 2^n, e.g.:

         Bias = 2^8  = 100000000
         Subtract 25 =  00011001
         Equals      =  11100111


In other words, to find the two's complement representation of a negative number, find the one's complement of it, and add one. The important thing to notice is that the range of an 8 bit variable using a two's complement representation is -128 to 127, as opposed to -127 to 127 using one's complement. Thus, the absolute value of the largest negative number cannot be represented (i.e. we cannot represent +128). Since the itoa() function in Chapter 3 handles negative numbers by reversing the sign of the number before processing, then adding a '-' to the string, passing the largest negative number will result it in being translated to itself:

                 -128            : 10000000
                 One's complement: 01111111
                 Subtract 1      : 10000000


Therefore, because (n /= 10) will be negative, the do-while loop will run once only, and will place in the string a '-', followed by a single character, (INT_MIN % 10 + '0'). We can remedy these two bugs in the following way: 1 - change 'while ((n /= 10) > 0)' to 'while (n /= 10)'. Since any fractional part is truncated with integer division, n will eventually equal zero after successive divides by 10, and 'n /= 10' will evaluate to false sooner or later. 2 - change 'n % 10 + '0 to 'abs(n % 10) + '0, to get the correct character. EX3_4.C shows the revised function, which will run correctly regardless of the number representation.



/*

  EX3_4.C
  =======
  
  Suggested solution to Exercise 3-4
  
*/
    
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

void itoa(int n, char s[]);
void reverse(char s[]);

int main(void) {
    char buffer[20];
    
    printf("INT_MIN: %d\n", INT_MIN);
    itoa(INT_MIN, buffer);
    printf("Buffer : %s\n", buffer);
    
    return 0;
}

void itoa(int n, char s[]) {
    int i, sign;
    sign = n;
    
    i = 0;
    do {
        s[i++] = abs(n % 10) + '0';
    } while ( n /= 10 );
    if (sign < 0)
        s[i++] = '-';
    
    s[i] = '\0';
    reverse(s);
}

void reverse(char s[]) {
    int c, i, j;
    for ( i = 0, j = strlen(s)-1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

      


Suggestion by LingTalfi I used an unsigned int instead of a signed one

#include <stdio.h>
#include <string.h>
#include <limits.h>

void reverse(char s[]) {
    int length = strlen(s);
    int c, i, j;

    for (i = 0, j = length - 1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}

void itoa(int n, char s[]) {

    int i, sign;
    unsigned int n2;
    i = 0;


    if ((sign = n) < 0) {
        n2 = -n;
    }
    else {
        n2 = n;
    }

    do {
        s[i++] = (n2 % 10) + '0';
    }
    while ((n2 /= 10) > 0);
    if (sign < 0) {
        s[i++] = '-';
    }
    s[i] = '\0';

    reverse(s);


}


main() {


    char s[128];
    itoa(INT_MIN, s);
    printf("%d is converted to %s.\n", INT_MIN, s);

}


Solution by Revc

I use a formula

rem(a, b) = rem(rem(a + c, b) - c, b)

to avoid the overflow in expression

 n = -n 

.

Here is a brief proof: See Remainder in Wikipedia to get more information. I shall quote the following:

If a and d are integers, with d non-zero, it can be proven that there exist unique integers q and r, 
 
such that a = qd + r and 0 ≤ r < |d|. The number q is called the quotient, while r is called the remainder.
 

Say

a = k1 * b + s1, a + c = k2 * b + s2 

,

so

c = s2 - s1 + (k2 - k1) * b 

.

According to the assumption,

 
rem(rem(a + c, b) - c, b) 
= rem(s2 - c , b)

and

s2 - c = (k1 - k2) * b + s1

,

since quotient and remainder are unique,

thus

rem(s2 - c, b) = s1 = rem(a,b)

.

/* itoa:  convert n to characters in s */
void itoa(int n, char s[])
{
	int i, sign;
	i = 0;
	sign = n;

	if (n < 0)
	{
		s[i++] = (((-(n + 1)) % 10) + 1) % 10 + '0'; // add one to negative for avoiding the overflow of -(2^wordsize - 1)
		n /= 10;
		n = -n;
	}
	else
	{
		s[i++] = n % 10 + '0';
		n /= 10;
	}

	while (n > 0)
	{
		s[i++] = n % 10 + '0';  /* get next digit */
		n /= 10;
	}
	if (sign < 0)
		s[i++] = '-';
	s[i] = '\0';
	reverse(s);
}


Solution by Codybartfast (category 0)

The original itoa doesn't work because the range of negative numbers is one greater than the range of positive numbers. So instead of converting negative numbers to positive, we can convert positive to negative and use the original method with just a few small changes:

void itoa(int n, char s[])
{
	int i, sign;
	if ((sign = n) > 0)
		n = -n;
	i = 0;
	do {
		s[i++] = '0' - (n % 10);
	} while ((n /= 10) < 0);
	if (sign < 0)
		s[i++] = '-';
	s[i] = '\0';
	reverse(s);
}

This will work with C99 but isn't strictly C89 compliant. With C99 I think we can assume -13/10 is -1 and -13%10 is -3 because division will truncate towards zero. However, C89 also allowed for floor towards negative infinity. In that case -13/10 = -2 with -13%10 = 7 was also valid. Both these satisfy the C89 requirement that "(a/b)*b + a%b = a" (A7.6, page 205). This version checks the behaviour of quotient and then adjusts the result of % if needed:

void itoa89(int n, char s[])
{
	int i, sign;

	/* 
	 * Test if we're getting floor quotient -2, instead of 'normal' 
	 * truncated quotient, -1.  If so, % will give positive remainders
	 * for negative dividends and we need to subtract 10 to get 'normal'
	 * negative remainders.
	 */

	int adj = ((-13 / 10) == -2) ? 10 : 0;

	if ((sign = n) > 0)
		n = -n;
	i = 0;
	do {
		/* adj will ensure we get a negative remainder */
		s[i++] = '0' - ((n % 10) - adj);
	} while ((n /= 10) < 0);
	if (sign < 0)
		s[i++] = '-';
	s[i] = '\0';
	reverse(s);
}

Solution by Miguel Degrossoli

/* Exercise 3-4. In a two's complement number representation, out version of 
 * "itoa" does not handle the largest negative number, that is, the value of n
 * equal to -[2^(wordsize-1)]. Explain why not. Modify it to print that value
 * correctly, regardless of the machine on which it runs. */

/* Let's think of a signed char, just because it's 8-bits long. The number -128
 * in binary is 10000000 (in a two's complement machine). To obtain its positive
 * counterpart (128) you find the two's complement of 10000000. You'll end-up
 * finding that the two's complement of 10000000 is exactly 10000000. The
 * computer cannot tell the difference, and it'll rekon this binary as -128
 * still, as MSB = 1. And that's why what tells a negative from a positive
 * number in a computer is the MSB (1 for negative, 0 for positive).
 *
 * The original "itoa" tries to convert the number to positive (i.e., calcs the
 * two's complement of the number), and that's not possible (that's why INT_MAX
 * equals |INT_MIN| - 1). */

#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#define MAXLEN 13	/* maximum string length */
#define COUNT   8	/* how many numbers to convert */

void reverse(char s[]);
void itoa(int n, char s[]);

int main() {

	int i, number[] = {15, -15, 6, -6, 1983, -1983, INT_MIN, INT_MAX};
	char s[MAXLEN];
	
	for (i == 0; i < COUNT; i++)
	{
		itoa(number[i], s);
		printf("%s\n", s);
	}
	exit(0);
}

void reverse(char s[]) {

	int i, n;
	char line[MAXLEN];

	for (i = 0; s[i] != '\0' && i < MAXLEN; ++i)
		;

	n = 0;
	if (i == 0 || s[i] == '\0')
		line[i] = '\0';

	if (i > 0)
	{
		for (--i; i >=0; --i) {
			line[i] = s[n];
			n++;
		}
		for (i = 0; line[i] != '\0'; i++)
			s[i] = line[i];
	}
}

void itoa(int n, char s[])
{
	int i, sign = 1;

	if (n < 0)
		sign = (-1);

	i = 0;

	do
	{
		s[i++] = (n % 10) * sign + '0';
	} while ((n /= 10) != 0);

	if (sign < 0)
		s[i++] = '-';

	s[i] = '\0';

	reverse(s);
}
Personal tools