On Stack Overflow, some people have already answered the question of converting an integer in binary notation to two's complement. Anyway, I did not find any of the answers I saw satisfactory because they do not consider the number of bits being predetermined. Also, the code I found on Stack Overflow crashes the terminal or gives strange errors with large numbers.
For these reasons, I have rewritten the following Bash function from scratch, which seems to accomplish the task I wish to give it correctly. I have commented on the code for clarity.
As you can see, I intentionally generate an overflow, in the line: x=$((2**($bits-1)-$x));
My question is whether this code is always reliable in Bash since, in other languages, the overflow is an error condition; here, however, I use it to get the desired result.
# Given a decimal number, prints its two's complement with the number of bits used by Bash
twos() {
x=$1; # input number in base 10
msb="0"; # the "most significant bit" is 0 for positive integers, 1 for negative integers
bits=$(getconf LONG_BIT); # detect the machine architecture, 32bit or 64bit
if [ "$x" -lt 0 ]; then
# the input number $x is negative
x=$((2**($bits-1)-$x)); # 2^(bits-1)-1 is the max integer number, -$x is positive, so it's an overflow
msb="1"; # "most significant bit" of negative numbers
fi
out=$(echo "obase=2;$x" | bc | tr -dc '0-9'); # conversion of $x to binary base, any sign is removed
n=$(($bits-1-${#out})); # number of zeros to add
if [ "$n" -gt 0 ]; then
zeros=$(printf '%0.s0' $(seq 1 $n)); # string consisting only of zeros
else
zeros=""; # deletes the variable that may be left in memory from a previous function call
fi
echo $msb$zeros$out; # prints the two's complement
}
Some examples:
$ twos 0
0000000000000000000000000000000000000000000000000000000000000000
$ twos 1
0000000000000000000000000000000000000000000000000000000000000001
$ twos 2
0000000000000000000000000000000000000000000000000000000000000010
$ twos 100
0000000000000000000000000000000000000000000000000000000001100100
$ twos -1
1111111111111111111111111111111111111111111111111111111111111111
$ twos -2
1111111111111111111111111111111111111111111111111111111111111110
$ twos -100
1111111111111111111111111111111111111111111111111111111110011100
$ twos 9223372036854775807
0111111111111111111111111111111111111111111111111111111111111111
$ twos -9223372036854775808
1000000000000000000000000000000000000000000000000000000000000000
CodePudding user response:
A bit simpler:
twos() {
n=$(getconf LONG_BIT)
printf 'obase=2; 2^%d %d\n' "$n" "$1" | bc | sed -E "s/.*(.{$n})$/\1/"
}
twos 100
0000000000000000000000000000000000000000000000000000000001100100
This simply uses bc
to add 2^n
(where n
is 32 or 64), print in base 2 and keeps only the n
least significant bits.
Note that technically the two's complement of a number is its opposite. So what you are looking for is more the binary representation of a number in two's complement.