two's complement of numbers in python

Question:

I am writing code that will have negative and positive numbers all 16 bits long with the MSB being the sign aka two’s complement. This means the smallest number I can have is -32768 which is 1000 0000 0000 0000 in two’s complement form. The largest number I can have is 32767 which is 0111 1111 1111 1111.

The issue I am having is python is representing the negative numbers with the same binary notation as positive numbers just putting a minus sign out the front i.e. -16384 is displayed as -0100 0000 0000 0000 what I want to be displayed for a number like -16384 is 1100 0000 0000 0000.

I am not quite sure how this can be coded. This is the code i have. Essentially if the number is between 180 and 359 its going to be negative. I need to display this as a twos compliment value. I dont have any code on how to display it because i really have no idea how to do it.

def calculatebearingActive(i):

    numTracks = trackQty_Active
    bearing = (((i)*360.0)/numTracks)
    if 0< bearing <=179:
        FC = (bearing/360.0)
        FC_scaled = FC/(2**(-16))
        return int(FC_scaled)

    elif 180<= bearing <=359:
        FC = -1*(360-bearing)/(360.0)
        FC_scaled = FC/(2**(-16))
        return int(FC_scaled)

    elif bearing ==360:
        FC = 0
        return FC
Asked By: user3299406

||

Answers:

If you actually want to store the numbers using 16 bits, you can use struct.

import struct

>>> struct.pack('h', 32767)
'xffx7f'
>>> struct.pack('h', -32767)
'x01x80'

You can unpack using unpack

>>> a = struct.pack('h', 32767)
>>> struct.unpack('H', a)
32767
Answered By: Jayanth Koushik

Since you haven’t given any code examples, I can’t be sure what’s going on. Based on the numbers in your example, I don’t think you’re using bin(yourint) because you’re output doesn’t contain 0b. Maybe you’re already slicing that off in your examples.

If you are storing your binary data as strings, you could do something like:

    def handle_negatives(binary_string):
        If binary_string < 0:
            binary_string = '1' + str(binary_string)[1:]
        Return binary_string
Answered By: Z_AHK

If you’re doing something like

format(num, '016b')

to convert your numbers to a two’s complement string representation, you’ll want to actually take the two’s complement of a negative number before stringifying it:

format(num if num >= 0 else (1 << 16) + num, '016b')

or take it mod 65536:

format(num % (1 << 16), '016b')
Answered By: user2357112

The two’s complement of a value is the one’s complement plus one.

You can write your own conversion function based on that:

def to_binary(value):
    result = ''
    if value < 0:
        result = '-'
        value = ~value + 1
    result += bin(value)
    return result

The result looks like this:

>>> to_binary(10)
'0b1010'
>>> to_binary(-10)
'-0b1010'

Edit: To display the bits without the minus in front you can use this function:

def to_twoscomplement(bits, value):
    if value < 0:
        value = ( 1<<bits ) + value
    formatstring = '{:0%ib}' % bits
    return formatstring.format(value)

>>> to_twoscomplement(16, 3)
'0000000000000011'
>>> to_twoscomplement(16, -3)
'1111111111111101'
Answered By: mars

Here are some caveats in printing complements(1s and 2s) in binary form, in python:

UNSIGNED RANGE:      0 to (2^k)-1 for k bit number 
               ex:   0 to (2^32)-1 numbers
               ex:   0 to 7 for 3 bit unsigned numbers (count = 8)

SIGNED RANGE:       -2^(k-1) to +2^(k-1)-1 for 1+k bit number (k-1 is for dividing current range k into two equal half)
               ex:  -2^31 to +(2^31)-1 numbers  
               ex   -8 to +7 for 1+3 bit signed numbers (count = 8)

bin(int)->str converts an integer to binary string
   CAVEAT: 1. Since in python there is no limit to length of integer
           for ~x or !x (1s_complement/not/negate) we can't determine how many bits after MSB needs to be 1
           so python just prints out unsigned value of target negative number in binary format with a
           '-' sign in the beginning
           ex: for x = b'01010'(10) we get ~x = -0b'1011' (-11) 
               but we should be getting -16+5 = -11 
               (-b'10000'+b'00101') = -b'10101' (-11 signed) or (21 unsigned)
               to get real binary value after negation(or 1s complement) one could simulate it
               NOTE:  2^0 is always 1, so (2**0 == 1) in python
               NOTE:  (1 << k) is always 2^k (left shift is 2 raised to the power k)
               ex: bin((1 << k)-1 - x) which is ((2^k)-1)-x  (1s complement)
               ex: bin((1 << k)-1 - x) + 1 which is (2^k)-x  (2s complement)
          2. Same goes for reverse parsing of signed binary string to int: 
             ex: int("-0b'0101'", 2) gives -5 but instead it actually is -11 assuming -0b represents all bits 
                 from MSB till current to be like 1111......0101 which is actually -16+5 = -11
                 BUT due to PYTHON's limitation of representing signed binary values we need to adhere to 
                 current way of parsing considering unsigned binary strings with sign in front for -ve numbers
# NOTE: how the significant prefix zeros doesn't matter in both +ve and -ve cases

# Byte type inputs
x = b'+1010'            # valid +ve number byte string
x = b'1010'             # valid +ve number byte string
x = b'-1010'            # valid -ve number byte string
x = b'+01010'           # valid +ve number byte string
x = b'01010'            # valid +ve number byte string
x = b'-01010'           # valid -ve number byte string

int(b'101')             # interprets as base 10 for each digit
int(b'101', 2)          # interprets as base 2 for each digit
int(b'101', 8)          # interprets as base 8 for each digit
int(b'101', 10)         # interprets as base 10 for each digit
int(b'101', 16)         # interprets as base 16 for each digit

# String type inputs
x = '+1010'            # valid +ve number string
x = '1010'             # valid +ve number string
x = '-1010'            # valid -ve number string
x = '+01010'           # valid +ve number string
x = '01010'            # valid +ve number string
x = '-01010'           # valid -ve number string

int('101')             # interprets as base 10 for each digit
int('101', 2)          # interprets as base 2 for each digit
int('101', 8)          # interprets as base 8 for each digit
int('101', 10)         # interprets as base 10 for each digit
int('101', 16)         # interprets as base 16 for each digit

# print(bin(int(x, 2)), int(x,2), ~int(x, 2), bin(~int(x,2)), "-"+bin((1<<k)-1 - int(x,2)))

k = 5                   # no of bits
assert 2**0 == 1        # (2^0 is always 1)
_2k = (1 << k)          # (2^k == left shift (2^0 or 1) by k times == multiply 2 by k times)
x = '01010'             # valid +ve number string
x = int(x,2)
print("input:", x)      # supposed to be 1s complement of binStr but due to python's limitation, 
                        # we consider it -(unsigned binStr)
_1s = '-'+bin((_2k-1)-x)
print("1s complement(negate/not): ", _1s, ~x)
_2s = '-'+bin(_2k-x)
print("2s complement(1s +1):      ", _2s, ~x+1)

output:
k = 5 (5 bit representation)
input: 10
1s complement(negate/not):  -0b10101 -11
2s complement(1s +1):       -0b10110 -10

k=32 (32 bit representation)
input: 10
1s complement(negate/not):  -0b11111111111111111111111111110101 -11
2s complement(1s +1):       -0b11111111111111111111111111110110 -10
Answered By: mahee96

Python can hold unlimited integer values, the bit representation will adopt to hold any number you put. So such technical details as two complement does not make sense in this context. In C ‘b1111111111111111’ means -1 for int16 and 65535 for uint16 or int32. In python it is always is 65535 as the int will adopt to hold such values.

I think this is why they opted to add – in front of negative numbers regardless of string representation (binary, oct, hex, decimal …).

If you wish to replicate the C behaviour and get the negative represented in two complement form you have the following options:

1 int > uint > bin using numpy

The most straight forward way is to dump the value as signed limited int and read it as unsigned.

If you have access to numpy the code is pretty easy to read:

>>> bin(np.int16(-30).astype(np.uint16))
'0b1111111111100010'
>>> bin(np.int16(-1).astype(np.uint16))
'0b1111111111111111'
>>> bin(np.int16(-2).astype(np.uint16))
'0b1111111111111110'
>>> bin(np.int16(-16).astype(np.uint16))
'0b1111111111110000'

2 int > uint > bin using struct

You can do the similar think with struct but it is slightly harder to understand

>>> bin(struct.unpack('>H', struct.pack('>h', 30))[0])
'0b1111111111100010'
>>> bin(struct.unpack('>H', struct.pack('>h', -1))[0])
'0b1111111111111111'
>>> bin(struct.unpack('>H', struct.pack('>h', -2))[0])
'0b1111111111111110'
>>> bin(struct.unpack('>H', struct.pack('>h', -16))[0])
'0b1111111111110000'

Note: h is signed and H is unsigned int 16, and ‘>’ stands for bigendian it comes handy if you want to read bytes directly without converting them back to int

3. int using struct, then read byte by byte and convert to bin

>>> ''.join(f'{byte:08b}' for byte in struct.pack('>h', -1<<15))
'1000000000000000'
>>> ''.join(f'{byte:08b}' for byte in struct.pack('>h', -1))
'1111111111111111'
>>> ''.join(f'{byte:08b}' for byte in struct.pack('>h', -2))
'1111111111111110'
>>> ''.join(f'{byte:08b}' for byte in struct.pack('>h', -16))
'1111111111110000'

Note this has some quirks, as you need to remember to enforce the byte binary representation to be 8 digits long with ’08’.

3 Directly from math,

Lastly you can go check what wikipedia says about 2 complement representation and get it implemented directly from the math formula Two complement > Subtraction from 2N

>>> bin(2**16 -16)
'0b1111111111110000'
>>> bin(2**16 -3)
'0b1111111111111101'

This looks super simple but it is hard to understand if you are not versed in the way 2 complement representation works.

Answered By: Piotr Czapla
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.