12

I have to convert a given 16 bit integer into two 8 bit integers, which are then taken and used as output, where they are headed takes the two 8 bit integers and recombines them as 16 bit input (unfortunately out of my control). My solution works, but feels unclean. For the coarse number I am bit shifting the original number, and for the fine number I am looking at it modulo 256.

So should I be doing floor division for the coarse number, or should I be taking the lowest 8 bits for the fine number (and if so how?)?

Or am I crazy and using two different methods to split the number is not a problem?

def convert(x):
    ''' convert 16 bit int x into two 8 bit ints, coarse and fine.

    '''
    c = x >> 8  # The value of x shifted 8 bits to the right, creating coarse.
    f = x % 256  # The remainder of x / 256, creating fine.
    return c, f
nrn
  • 184
  • 1
  • 1
  • 7
  • 3
    How about just `return divmod(x, 256)`? – Mark Dickinson Jun 26 '10 at 09:35
  • I went with: c = x >> 8 f = x & 0xff # which I didn't know about before. It seems more true to what I am doing then divmod or % and /, and likely too be closer to the same operation preformed at the other end to recombine them (I hope). – nrn Jun 26 '10 at 09:59

7 Answers7

18

I would do

c = (x >> 8) & 0xff
f = x & 0xff

It is safer, see e.g.

>>> (10303 >> 8) & 0xff
40
>>> (1030333333 >> 8) & 0xff
163
>>> (1030333333 >> 8) 
4024739

Since in python you can't control if the number is or not a 16bit, you have to force it into an at most 16-bit value. This is not needed if you're sure to have a 16-bit value, but this way the function is more general and allows you to be interested only in 16-bit values, no matter what the container contains.

sigjuice
  • 28,661
  • 12
  • 68
  • 93
ShinTakezou
  • 9,432
  • 1
  • 29
  • 39
  • read "Since in python you can't "controll" if the number is or not a 16bit" adding _afaik_ and interpreting it as saying there's no a fixed bit-width container (like uint16 so to say) in python, _afaik_ – ShinTakezou Jun 26 '10 at 09:39
  • I should have mentioned that if x > 16 bits something has gone horribly wonky, so it throws an error if c > 255 on down the road. – nrn Jun 26 '10 at 09:46
  • you can drop the checks for c > 255, or keep them and don't And the result; if you can't get more than 65535 "by design", you can drop the & and the checks for c > 255; but I'd still use & instead of % (edited in my ans, since forgot to change after copy-pasting), as other has said. – ShinTakezou Jun 26 '10 at 09:53
9

In python, bit-fiddling doesn't have any particular advantage, so I would go with:

c, f= divmod(your_number, 256)

EDIT: To make your intention even more obvious to the powers-of-two-challenged source viewer (if such a beast exists), you can replace the plain 256 with much more colourful alternatives, like 1<<8, 2**8, 0x100 or 0400 (that is 0o400 for Python 3). The constant folding done by the peephole optimizer since Python 2.5 ensures that any of them is exactly the same like using 256 (I'm obviously talking about the former two alternatives, which are expressions that evaluate to 256; the latter two are the constant 256).

$ python
Python 2.6.4 (r264:75706, Dec  7 2009, 18:45:15)
[GCC 4.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis
>>> dis.dis(compile("c, f= divmod(your_number, 1<<8)", "", "exec"))
  1           0 LOAD_NAME                0 (divmod)
              3 LOAD_NAME                1 (your_number)
              6 LOAD_CONST               3 (256)
              9 CALL_FUNCTION            2
             12 UNPACK_SEQUENCE          2
             15 STORE_NAME               2 (c)
             18 STORE_NAME               3 (f)
             21 LOAD_CONST               2 (None)
             24 RETURN_VALUE
tzot
  • 92,761
  • 29
  • 141
  • 204
  • 1
    or even better `c, f= divmod(your_number, 1<<8)` to show clearly that you're splitting at the eighth bit :-) – fortran Jun 28 '10 at 13:19
  • 1
    @fortran: Sure :) I somehow remembered the joke where one programmer asks a loan of $1000 from a fellow programmer, who replies: “why don't we round the loan to $1024?” – tzot Jun 28 '10 at 15:32
2

You should be consistent, if the intent of the operations is arithmetical, use modulo and division, if it's just for raw bit manipulation, use shift and mask.

fortran
  • 74,053
  • 25
  • 135
  • 175
2

You say you're using these numbers as output, which suggests that they're going to be converted into strings at some point down the line. With that in mind, I'd suggest you take a look at the struct module, which is designed for precisely this sort of thing (packing numbers into strings of binary data). As a bonus, you get built-in error checking for the case where x is greater than 65535 (so that if something is horribly wonky in your program, you'll get an exception). For example,

s = struct.pack('>H', x)

is the equivalent of

if x > 65535:
    raise struct.error(...)
c, f = convert(x)
s = chr(c) + chr(f) # big-endian (network) byte ordering

If you need the other byte ordering, you can write

s = struct.pack('<H', x)

If you have a whole bunch of numbers to convert at once, struct.pack can do them in bunches:

x = [10333, 10475, 3021, ...] # for example
s = struct.pack('>' + 'H' * len(x), *x)
David Z
  • 128,184
  • 27
  • 255
  • 279
0

If you use the two halves of the number in different places, I'd recommend having two separate functions, but if you're going to use them in the same place, one function will work just as well.

There are several correct ways to split the number, so eventually it all comes down to personal preference. Your code will work fine, just so long as you only pass in numbers that are at most 16 bits long. (which probably won't be much of a problem, but you should be aware of it)

Raceimaztion
  • 9,494
  • 4
  • 26
  • 41
  • If you're factoring out the application of a single operator, either you should have a specific reason for doing it in that case, or else you don't understand the reason for factoring things out. A function name that you've invented is likely to be less clear than a standard operator that anyone reading your code should know about, and of course you then have to go looking for the function definition to find out what it really does. Excessive factorization, moving complexity into the call graph, is in my view the modern replacement for goto-spaghetti. –  Jun 26 '10 at 09:42
0

I would use the bitwise & rather than %. It probably makes little difference these days for short integers, but in the wider view, the & operator is potentially more efficient.

There may be some issue about how % handles negative numbers, but I doubt that that's relevant here.

0

Here is an alternative solution I like:

def convert(x, n_bytes=2, order='big'):
    msb, *lsb = x.to_bytes(n_bytes, byteorder=order)
    return (msb, *lsb)

you can change the number of output bytes by changing n_bytes. There order can be switched by setting order='little'

exmample output

In [51]: convert(0x00)
Out[51]: (0, 0)

In [52]: convert(0xff)
Out[52]: (0, 255)

In [53]: convert(0x01ff)
Out[53]: (1, 255)

In [55]: convert(0x7fff)
Out[55]: (127, 255)

error if value is to big:

In [54]: convert(0xffffff)
---------------------------------------------------------------------------
OverflowError                             Traceback (most recent call last)
Input In [54], in <cell line: 1>()
----> 1 convert(0xffffff)

Input In [48], in convert(x, n_bytes, order)
      1 def convert(x, n_bytes=2, order='big'):
----> 2     msb, *lsb = (x).to_bytes(n_bytes, byteorder=order)
      3     return (msb, *lsb)

OverflowError: int too big to convert
n4321d
  • 1,059
  • 2
  • 12
  • 31