Reputation: 1426
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
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
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
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