21

I've created a method to convert an int to a bitfield (in a list) and it works, but I'm sure there is more elegant solution- I've just been staring at it for to long.

I'm curious, how would you convert a int to a bitfield represented in a list?

def get(self):
    results = []

    results.append(1 if (self.bits &   1) else 0)
    results.append(1 if (self.bits &   2) else 0)
    results.append(1 if (self.bits &   4) else 0)
    results.append(1 if (self.bits &   8) else 0)
    results.append(1 if (self.bits &  16) else 0)
    results.append(1 if (self.bits &  32) else 0)
    results.append(1 if (self.bits &  64) else 0)
    results.append(1 if (self.bits & 128) else 0)

    return results

def set(self, pin, direction):
    pin -= 1
    if pin not in range(0, 8): raise ValueError

    if direction: self.bits |= (2 ** pin)
    else: self.bits &=~(2 ** pin)
tMC
  • 18,105
  • 14
  • 62
  • 98

6 Answers6

44

How about this:

def bitfield(n):
    return [int(digit) for digit in bin(n)[2:]] # [2:] to chop off the "0b" part 

This gives you

>>> bitfield(123)
[1, 1, 1, 1, 0, 1, 1]
>>> bitfield(255)
[1, 1, 1, 1, 1, 1, 1, 1]
>>> bitfield(1234567)
[1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1]

This only works for positive integers, though.

EDIT:

Conversion to int using int() is a bit overkill here. This is a lot faster:

def bitfield(n):
    return [1 if digit=='1' else 0 for digit in bin(n)[2:]]

See the timings:

>>> import timeit
>>> timeit.timeit("[int(digit) for digit in bin(123)[2:]]")
7.895014818543946
>>> timeit.timeit("[123 >> i & 1 for i in range(7,-1,-1)]")
2.966295244250407
>>> timeit.timeit("[1 if digit=='1' else 0 for digit in bin(123)[2:]]")
1.7918431924733795
Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
  • `[123 >> i & 1 for i in range(7,-1,-1)]` is the fastest on my machine. – tMC Apr 25 '12 at 19:32
  • @tMC: I've redone the timings on both my PCs (Win 7 Ultimate 64bit), under Python 2.7.3 and 3.2.3, and my solution was always faster by at least 20 % (Python 2) and 45 % (Python 3). – Tim Pietzcker Apr 25 '12 at 19:38
  • the difference is negligible. I just thought it was interesting that i got different results on my Linux laptop. – tMC Apr 25 '12 at 19:49
  • This is reversing the correct order of the bitfields because it's reading the chars of the string from left to right. e.g. `bitfield(8) = [1, 0, 0, 0]` – dead10ck Jan 13 '23 at 16:31
  • Huh, right. Nobody noticed that for over ten years :) - well, if the reverse order is desired, it's easy to reverse the list afterwards (`...[::-1]`) – Tim Pietzcker Jan 16 '23 at 11:58
29

This doesn't use bin:

 b = [n >> i & 1 for i in range(7,-1,-1)]

and this is how to handle any integer this way:

 b = [n >> i & 1 for i in range(n.bit_length() - 1,-1,-1)]

See bit_length.

If you want index 0 of the list to correspond to the lsb of the int, change the range order, i.e.

b = [n >> i & 1 for i in range(0, n.bit_length()-1)]

Note also that using n.bit_length() can be a point of failure if you're trying to represent fixed length binary values. It returns the minimum number of bits to represent n.

jpgeek
  • 4,991
  • 2
  • 28
  • 26
georg
  • 211,518
  • 52
  • 313
  • 390
  • This is perfect- I knew there was a list comprehension I was missing – tMC Apr 25 '12 at 19:07
  • 1
    It may handle any integers in this way: [n >> i & 1 for i in range(n.bit_length() - 1,-1,-1)] – mennanov Feb 25 '16 at 08:20
  • @mennanov: thanks, added (btw, feel free to edit posts if you have something valuable to add). – georg Feb 25 '16 at 09:31
  • For the sake of performance, you should use `%2` instead of `&1`, even though the latter looks smarter: `timeit.timeit("n=193; n%2")` returns `0.043587817999650724`, while `timeit.timeit("n=193; n&1")` returns `0.0545583209986944` (I set `n` to an arbitrary value so as to avoid optimizations). – Right leg Jan 28 '19 at 12:37
  • 1
    The last example should be `range(0, n.bit_length())` as range stop value is not inclusive. – DurandA May 25 '21 at 17:06
4

Try

>>>n=1794
>>>bitfield=list(bin(n))[2:]
>>>bitfield
['1', '1', '1', '0', '0', '0', '0', '0', '0', '1', '0']

This does not work for negative n though and as you see gives you a list of strings

snugglo
  • 259
  • 1
  • 5
3

Array with fixed length

Array with fixed length:

>>> '{0:07b}'.format(12)
'0001100'

Do you believe string is also an array? No? See:

>>> [int(x) for x in '{0:07b}'.format(12)]
[0, 0, 0, 1, 1, 0, 0]
Amir Fo
  • 5,163
  • 1
  • 43
  • 51
1

Does not work for negative values

>>> import numpy as np
>>> [int(x) for x in np.binary_repr(123)]
[1, 1, 1, 1, 0, 1, 1]
orangepips
  • 9,891
  • 6
  • 33
  • 57
0

I'm doing this for my program where you specify a template to get your values from an int:

def field(template, value):
    sums = [int(v) if v.__class__==str else len(bin(v))-2 for v in template]
    return [(value>> (sum(sums[:i]) if i else 0) )&(~(~0<<int(t)) if t.__class__==str else t) for i,t in enumerate(template)]

how to use
in the template, specify ints relating to your bit-sizes:

field([0b1,0b111,0b1111], 204) #>>> [0, 6, 12]

or you can specify the bit-size of each value needed using strings: (noob friendly)

field(['1','3','4'], 204) #>>> [0, 6, 12]

EDIT: and vice versa: (separate code)

field(['1','3','4'], [0, 6, 12]) #>>> 204
field([0b1,0b111,0b1111], [0,3,9]) #>>> 150

the code:

def field(template, value):
    res = 0
    for t, v in zip(template, value)[::-1]: res = (res << (t.bit_length() if t.__class__ is int else int(t)) )|v
    return res

EDIT2: faster code^

Tcll
  • 7,140
  • 1
  • 20
  • 23