Halsi
Halsi

Reputation: 53

Converting a negative decimal into binary in C

I'm currently working on a program that (among others) has to convert a decimal number into binary, octal & hexadecimal. This already works with this code:

    int e = 0;
    }
        while(i != 0){
            str[e] = (i%b) + '0';
            i = i / b;
            if(str[e] > '9'){
                str[e] = str[e] + 7;
            }
            e++;

    }
    if(vorzeichen == -1){
        str[e] = '1';
        e++;
    }
    if(b == 16){
        str[e] = 'x';
        str[e+1] = '0';
    }
    else if(b == 8){
        str[e] = '0';
    }
}

b is the base (2 for binary, 8 for octal & 16 for hexa) and i is the number that i want to convert. This gives out a string of characters which i then reverse to get the correct number. Now if i try this with negative numbers, it gives out strings not only containing 0 and 1 but also /, which is '0' -1 on the ASCII table. For octal and decimal it also gives out characters below the '/' on the ASCII table. I've attempted different possible solutions but none seemed to give the desired result. What I read on the internet is that I have to use the 2s Complement I'm stuck trying to use it. It just doesn't seem to work for me.

Upvotes: 0

Views: 3378

Answers (3)

Nominal Animal
Nominal Animal

Reputation: 39308

When converting between different bases/radixes, always work on unsigned integer types.

Let's say you have long num you wish to convert. Use an unsigned long u. To represent negative values in two's complement format, you can use

if (num < 0)
    u = 1 + (~(unsigned long)(-num));
else
    u = num;

or even shorter,

unsigned long  u = (num < 0) ? 1 + (~(unsigned long)(-num)) : num;

This works on all architectures (except for num == LONG_MIN, in which case the above is technically undefined behaviour), even those that do not use two's complement internally, because we essentially convert the absolute value of num. If num was originally negative, we then do the two's complement to the unsigned value.


In a comment, chux suggested an alternative form which does not rely on UB for num == LONG_MIN (unless LONG_MAX == ULONG_MAX, which would be horribly odd thing to see):

unsigned long  u = (num < 0) ? 1 + (~((unsigned long)(-1 - num) + 1)) : num;

This may look "uglier", but a sane C compiler should be able to optimize either one completely away on architectures with two's complement integers. chux's version avoids undefined behaviour by subtracting the negative num from -1, thus mapping -1 to 0, -2 to 1, and so on, ensuring that all negative values are representable as a nonnegative long. That value is then converted to unsigned long. This gets incremented by one, to account for the earlier -1. This procedure yields the correct negation of num.

In other words, to obtain the absolute value of a long, you can use

unsigned long  abs_long(const long  num)
{
    return (num < 0) ? (unsigned long)(-1 - num) + 1u : (unsigned long)num;
}

Upvotes: 1

chux
chux

Reputation: 153338

% is the remainder function, not mod.

With b==2, i%b returns [-1, 0, 1]. This is not the needed functionality for str[e] = (i%b) + '0'; See ... difference between “mod” and “remainder”

This is the cause of '/' and "also gives out characters below the '/' ".


Build up the string from the "right"

With a 2's complement int, a simple approach is to convert to unsigned and avoid a negative result from %. Since code is using % to extract the least significant digit, walk the buffer from right to left.

#include <limits.h>

...
unsigned u = i;

// make a temporary buffer large enough for any string output in binary
//           v------v Size of `u` in "bytes"
//           |      |   v------v Size of a "byte" - commonly 8
char my_buff[sizeof u & CHAR_BIT + 1];
int e = 0;

// Form a pointer to the end so code assigns the least significant digits on the right
char *p = &my_buff[sizeof my_buff - 1];

// Strings are null character terminated
*p = '\0';

// Use a `do` loop to insure at least one pass. Useful when `i==0` --> "0"
do {
  p--; 
  p[e] = "0123456789ABCDEF"[u%b];  // Select desired digit
  u = u / b;
} while (u);

// "prepend" characters as desired
if(b == 16){
  *(--p) = 'x';
  *(--p) = '0';
}
else if(b == 8 && i != 0){
  *(--p) = '0';
}

strcpy(str, p);

Upvotes: 0

Rapha&#235;l Dalmon
Rapha&#235;l Dalmon

Reputation: 36

if you want to display a negative decimal you just can convert your int to a unsigned int like this :

unsigned int value = (unsigned int)i;

Now you only have to use value instead of i in your program and it will be fine. Here's a good explanation of why : Converting negative decimal to binary

Upvotes: 1

Related Questions