Search code examples
pythonpython-3.xbinarytwos-complement8-bit

Two's complement function outputs wrong result for -1


I am generating the input for an FPGA program to use the trapezoidal integration method. Basically, the functions of interest here are the invert() and twos_comp() functions; the rest is just testing (creating a square wave signal, and then iterating through and converting it into two's complement).

signals = []
bit_signals = []

def invert(bit_val):
    new_val = []
    for i in bit_val:
        new_val.append(str(int(not(int(i)))))
    return ''.join(new_val)

def twos_comp(val):
    if val < 0:
        bin_val = format(val, '08b')[1:]
        return format(int(invert(bin_val),2) + int('1', 2), '08b')
    else:
        bin_val = format(val, '08b')[1:]
        return bin_val

x = 0
signal = 1
while x <= 25:
    if x % 2 == 0:
        signal*=-1
    signals.append(signal)
    x+=1

print(signals)

for i in signals:
    bit_signals.append(twos_comp(i))

print(bit_signals)

The problem here is that this outputs the two's complement for 1 as 01111111, not 1111111. The output of invert() seems to be correct, the output for twos_comp() for positive numbers seems to be correct, and the generation of the signal also appears to be correct, so I think it must be something with the line

return format(int(invert(bin_val),2) + int('1', 2), '08b')

but looking around on SO and google this is how other people have handled adding in binary.

Please note that all inputs to twos_comp() will be 8 bits. Any help would be appreciated as to why this is not working. There are no outright errors, just an incorrect output.

You can run the code here.


Solution

  • Step through the values when val is -1:

    >>> format(-1, '08b')
    '-0000001'
    

    You may have already spotted the error—08b means 8 characters wide, not 8 digits. For a negative number, the - takes up 1 of the characters, so you only get 8 digits. But in case it isn't obvious why that's a problem, let's keep going:

    >>> format(val, '08b')[1:]
    '0000001'
    >>> invert('0000001')
    '1111110'
    >>> int(invert('0000001'), 2)
    126
    >>> int('1', 2) # BTW, why do you need this instead of just 1, exactly?
    1
    >>> 126 + 1
    127
    >>> format(127, '08b')
    01111111
    

    If you want a hacky solution (and I suspect you do, since you're already going back and forth between strings and numbers all over the place), just do this:

    bin_val = format(val, '09b')[-8:]
    

    That will work for both positive and negative numbers.