Reputation: 53
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
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
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
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