0

I implemented one's complement addition of 16 bit integers in python, however I am trying to see if there is a better way to do it.

 # This function returns a string of the bits (exactly 16 bits)
 # for the number (in base 10 passed to it)
 def get_bits(some_num):
        binar = bin(some_num)[2::]
        zeroes = 16 - len(binar)
        padding = zeroes*"0"
        binar = padding + binar
        return binar


# This function adds the numbers, and handles the carry over
# from the most significant bit
def add_bits(num1, num2):
        result = bin(int(num1,2) + int(num2,2))[2::]
        # There is no carryover
        if len(result) <= 16 :
                result = get_bits(int(result,2))
        # There is carryover
        else :
                result = result[1::]
                one = '0000000000000001'
                result = bin(int(result,2) + int(one,2))[2::]
                result = get_bits(int(result,2))
        return result

And now an example of running it would be:

print add_bits("1010001111101001", "1000000110110101")

returns :

0010010110011111

Is what wrote safe as far as results (Note I didn't do any negation here since that part is trivial, I am more interested in the intermediate steps)? Is there a better pythonic way to do it? Thanks for any help.

Cauchy
  • 1,677
  • 2
  • 21
  • 30
  • 2
    Well, first, if you're worried about writing Pythonic code, you should follow PEP 8, or some other reasonable style guide, so people don't have to try to look past the low-level weirdness to tell you if there's any high-level weirdness. – abarnert Apr 24 '15 at 08:31
  • Also, what do you mean by "the number (in base 10) passed to it"? It looks like you're just passing `int` values, which aren't in base 10. You could call them base 2, or even base 2**30 (because Python implements bignums with 30-bit chunks that it treats as atomic values to do C arithmetic on), but they're not base 10 in any sense. – abarnert Apr 24 '15 at 08:33
  • @abarnert I'm not sure what you mean by low level weirdness. If you can be more specific then I can clarify. – Cauchy Apr 24 '15 at 08:42
  • By "low level weirdness" I mean not following PEP 8 style, adding extra little fiddles like using `[1::]` instead of `[1:]`, etc. All those things stick out like a sore thumb, getting in the way of seeing the more important issues you're presumably asking about. – abarnert Apr 24 '15 at 08:43
  • Note that `format(some_int, '016b')` or `'{:0{bits}b}'.format(some_int, bits=16)` gives you a 16-bit zero-padded binary string, saving you quite a few lines of fiddling with strings. – jonrsharpe Apr 24 '15 at 08:43
  • @abarnert what do you mean that int is not base 10? If you open up your interpreter and you do basic operations between two integers then your result will be shown in base 10. – Cauchy Apr 24 '15 at 08:44
  • 1
    @Cauchy that's just their representation, and doesn't reflect the reality that Python/your computer is working on. Somewhere deep down it's all just ones and zeroes. – jonrsharpe Apr 24 '15 at 08:45
  • @jonrsharpe I am not saying that the cpu is doing arithmetic operations in base 10, I am just talking in terms of what I can see and how Iregularly thinks about integers when writing code in python, and that is in base 10. – Cauchy Apr 24 '15 at 08:47
  • 1
    @Cauchy you should just say it takes *"an integer"* (which can be represented in any base you like!), and write that in a [docstring](https://www.python.org/dev/peps/pep-0257/) rather than a comment. – jonrsharpe Apr 24 '15 at 08:49

2 Answers2

6

Converting back and forth between string and ints to do math is inefficient. Do the math in integers and use formatting to display binary:

MOD = 1 << 16

def ones_comp_add16(num1,num2):
    result = num1 + num2
    return result if result < MOD else (result+1) % MOD

n1 = 0b1010001111101001
n2 = 0b1000000110110101
result = ones_comp_add16(n1,n2)

print('''\
  {:016b}
+ {:016b}
------------------
  {:016b}'''.format(n1,n2,result))

Output:

  1010001111101001
+ 1000000110110101
------------------
  0010010110011111
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
1

Converting back and forth between numbers, lists of one-bit strings, and strings probably doesn't feel like a very Pythonic way to get started.

More specifically, converting an int to a sequence of bits by using bin(i)[2:] is pretty hacky. It may be worth doing anyway (e.g., because it's more concise or more efficient than doing it numerically), but even if it is, it would be better to wrap it in a function named for what it does (and maybe even add a comment explaining why you did it that way).


You've also got unnecessarily complexifying code in there. For example, to do the carry, you do this:

one = '0000000000000001'
result = bin(int(result,2) + int(one,2))[2::]

But you know that int(one,2) is just the number 1, unless you've screwed up, so why not just use 1, which is shorter, more readable and obvious, and removes any chance of screwing up?


And you're not following PEP 8 style.


So, sticking with your basic design of "use a string for the bits, use only the basic string operations that are unchanged from Python 1.5 through 3.5 instead of format, and do the basic addition on integers instead of on the bits", I'd write it something like this:

def to_bits(n):
    return bin(n)[2:]

def from_bits(n):
    return int(n, 2)

def pad_bits(b, length=16):
    return ["0"*length + b][-length:]

def add_bits(num1, num2):
    result = to_bits(from_bits(num1) + from_bits(num2))
    if len(result) <= 16: # no carry
        return pad_bits(result)
    return pad_bits(to_bits(from_bits(result[1:]) + 1))

But an even better solution would be to abstract out the string representation completely. Build a class that knows how to act like an integer, but also knows how to act like a sequence of bits. Or just find one on PyPI. Then your code becomes trivial. For example:

from bitstring import BitArray

def add_bits(n1, n2):
    """
    Given two BitArray values of the same length, return a BitArray
    of the same length that's the one's complement addition.
    """
    result = n1.uint + n2.uint
    if result >= (1 << n1.length):
        result = result % n1.length + 1
    return BitArray(uint=result, length=n1.length)

I'm not sure that bitstring is actually the best module for what you're doing. There are a half-dozen different bit-manipulating libraries on PyPI, all of which have different interfaces and different strengths and weaknesses; I just picked the first one that came up in a search and slapped together an implementation using it.

abarnert
  • 354,177
  • 51
  • 601
  • 671