0

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.

Halsi
  • 53
  • 1
  • 2
  • 9
  • `snprintf(str, size_of_str, "%o", integer_value)`? Replace `"%o"` with `"%x"` for hexadecimal. For binary I would recommend using bitwise operations like shifting and AND. – Some programmer dude Nov 23 '18 at 11:52
  • I can't use library functions – Halsi Nov 23 '18 at 11:55
  • Then I suggest you take some time to [learn how to debug your code](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). – Some programmer dude Nov 23 '18 at 11:59
  • This is the sort of situation where I suggest writing the algorithm on **paper** first and see how it behaves for different inputs. Once you have something solid going, it's easy to write it in code. Take your time to learn. – luci88filter Nov 23 '18 at 12:11

3 Answers3

1

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

1

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;
}
Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Good approach that does not rely on _two's complement internally_. Corner oops: "It is UB with 2's complement `i == INT_MIN` and `-num`. – chux - Reinstate Monica Nov 23 '18 at 16:40
  • @chux: I'm sure you mean `LONG_MIN`, as I'm using `long`, not `int`. I'm not sure it is worth avoiding the Undefined Behaviour here, nor how would one do so portably without an even larger integer type. Suggestions? – Nominal Animal Nov 23 '18 at 16:49
  • Yes `LONG_MIN` was meant, not `INT_MIN`. To avoid a larger type, `1 + (~((unsigned long)(-1 - num) + 1));` (correct, but ugly) or other code. – chux - Reinstate Monica Nov 23 '18 at 17:01
  • @chux: Is there anything in the C standards that says `LONG_MIN+LONG_MAX` cannot be less than -1, or greater than 0? If not, `LONG_MIN` is not the only case that needs special care. – Nominal Animal Nov 23 '18 at 17:05
  • Yes, "LONG_MIN+LONG_MAX cannot be less than -1, or greater than 0" is a consequence of C11 §6.2.6.2 2. On a pedantic note, `LONG_MAX == ULONG_MAX` is possible yet not have come across such a machine for decades. IMO, OP's goal lacks clarity for a highly portable solution- yet good enough solutions can be had. – chux - Reinstate Monica Nov 23 '18 at 17:11
  • @chux: Convinced, thus added the suggestion. Let me know if I mischaracterized or mistyped anything. – Nominal Animal Nov 23 '18 at 17:27
0

% 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);
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256