nico
nico

Reputation: 1426

How to convert an array of 27 bytes where each 3 bytes is a 2's complement number into its numeric equivalent?

I want to unpack a data package that has 9 numbers, each number is in a 2's complement format with 3 bytes. Thus 27 bytes in my package. I have this, which allows me to use struct.unpack with 4 byte numbers but multiplies my numbers by 16.

Works but multiplies output by 16

data = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

a = a[:3] + [0] + a[3:6] + [0] + a[6:9] + [0] + a[9:12] + [0] + a[12:15] + [0] + a[15:18] + a[18:21] + [0] + [0] + a[21:24] + [0] + a[24:27] + [0]

ch0, ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8 = struct.unpack('>lllllllll', bytearray(a))

For 2's complement with 4 bytes I would have to add 0xFF to the front of negative numbers and 0x00 to the front of the positive numbers.

Is there a smart way to perform that? What is the most efficient way to do it?

Upvotes: 1

Views: 123

Answers (3)

Blckknght
Blckknght

Reputation: 104842

I suggest parsing your 3-byte values as a single signed byte and an unsigned (16-bit) short. You can then use mathematical operators to recombine them into single number.

Here's a list comprehension that does it all in one step (given s as a bytearray or bytes string):

[(a<<16) + b for a, b in zip(*([iter(struct.unpack('>'+'bH'*9, s))]*2))]

The inner-most part does the parsing as I described above, using struct.unpack. Then I use the zip on two copies of the same iterator to get the byte/short pairs (this is a nifty trick!), and left-shift the byte by 16 bits before adding them together.

If you already have your bytes as unsigned integers, you don't even need to use struct.unpack, you can combine them directly in groups of three (with only a little bit of logic to get the sign right on the first byte):

[((a if a < 128 else a-256)<<16) + (b<<8) + c for a, b, c in zip(*([iter(data)]*3))]

Upvotes: 1

Mark Tolonen
Mark Tolonen

Reputation: 178179

If you are using Python 3, use the class method int.from_bytes. It can handle byte clusters of any length:

#!python3
data = bytes([0,0,0,0xff,0xff,0xff,0,0,1,0xff,0xff,0xfe,0,0,3,0xff,0xff,0xfd,0,0,4])
for i in range(0,len(data),3):
    print(int.from_bytes(data[i:i+3],'big',signed=True))

Output:

0
-1
1
-2
3
-3
4

There is also to_bytes, if you need to generate them as well:

>>> (-2).to_bytes(3,'big',signed=True)
b'\xff\xff\xfe'

Upvotes: 1

martineau
martineau

Reputation: 123541

You can use a helper function to extend the sign-bit of the third byte to make a fourth one:

import struct

data = [0x00, 0x00, 0x00,
        0xff, 0xff, 0xff,
        0x00, 0x00, 0x02,
        0xff, 0xff, 0xfd,
        0x00, 0x00, 0x04,
        0xff, 0xff, 0xfb,
        0x00, 0x00, 0x06,
        0xff, 0xff, 0xf9,
        0x00, 0x00, 0x08]

signed = lambda v: [0xff if v & 0x80 else 0x00,]
ch = [struct.unpack('>i', ''.join(map(chr, signed(data[i]) + data[i:i+3])))[0]
                                                    for i in range(0, len(data), 3)]

print(ch)  # -> [0, -1, 2, -3, 4, -5, 6, -7, 8]

Upvotes: 2

Related Questions