2010-08-23 4 views
5

Ich möchte eine Ganzzahl (int oder long) eine Big-Endian-Byte-Zeichenfolge konvertieren. Der Byte-String muss von variabler Länge sein, so dass nur die minimale Anzahl von Bytes verwendet wird (die Gesamtlängenlänge der vorhergehenden Daten ist bekannt, so dass die variable Länge abgeleitet werden kann).Wie konvertiert man eine Ganzzahl in eine Bytefolge mit variabler Länge?

Meine aktuelle Lösung ist

import bitstring 

bitstring.BitString(hex=hex(456)).tobytes() 

, die auf der endianness der Maschine hängt natürlich und gibt falsche Ergebnisse, da 0 Bits werden nicht vorangestellter hängen und.

Kennt jemand einen Weg, dies zu tun, ohne irgendeine Annahme über die Länge oder Endianess eines int zu machen?

+0

Muss dies nur für ein 'int' funktionieren, oder muss es auch für ein' long' funktionieren? – jchl

+0

Für 'long' habe ich das auch vergessen. Ich werde die Frage bearbeiten. –

+0

Dies kann einfach in jeder Version von Python ohne externe Abhängigkeiten getan werden - in jedem Fall möchten Sie ein BYTEstring, kein BITstring. –

Antwort

0

Wenn Sie mit Python 2.7 oder höher können Sie die bit_length Methode verwenden, um die Länge aufrunden auf das nächste Byte:

>>> i = 456 
>>> bitstring.BitString(uint=i, length=(i.bit_length()+7)/8*8).bytes 
'\x01\xc8' 

sonst kann man nur für Ganz byteness und Pad-Test mit wenn ein Null knabbert am Start benötigt:

>>> s = bitstring.BitString(hex=hex(i)) 
>>> ('0x0' + s if s.len%8 else s).bytes 
'\x01\xc8' 
+0

'bit_length' scheint eine saubere Lösung zu sein (obwohl ich auf Debian auf Python 2.6 bin). '(i.bit_length() + 7)/8 * 8' rundet die Länge auf eine Länge auf, die um 8 teilbar ist, habe ich recht? Das Endianness-Problem existiert auch noch. –

+0

Ich fand eine [Erklärung für die Rundung] (http://stackoverflow.com/questions/2403631/how-do-i-find-the-next-multiple-of-10-of-any-integer). Also bleibt nur das Endian-Problem bestehen. –

+0

'uint' ist ein Alias ​​für' uintbe', so dass auch das Endianess-Problem gelöst ist. –

6

So etwas wie das. Ungetestet (bis zur nächsten Änderung). Für Python 2.x. Nimmt an: n> 0.

tmp = [] 
while n: 
    n, d = divmod(n, 256) 
    tmp.append(chr(d)) 
result = ''.join(tmp[::-1]) 

Edit: getestet.

Wenn Sie keine Handbücher lesen, aber ich mag bitbashing anstelle des divmod Kaper, versuchen Sie dies:

d = n & 0xFF; n >>= 8 

Edit 2: Wenn die Zahlen relativ klein sind, können die folgenden schneller sein:

result = '' 
while n: 
    result = chr(n & 0xFF) + result 
    n >>= 8 

Edit 3: Die zweite Methode geht nicht davon aus, dass der Int bereits Bigendian ist. Hier ist, was in einem notorisch littleendian Umgebung passiert:

Python 2.7 (r27:82525, Jul 4 2010, 09:01:59) [MSC v.1500 32 bit (Intel)] on win32 
Type "help", "copyright", "credits" or "license" for more information. 
>>> n = 65539 
>>> result = '' 
>>> while n: 
...  result = chr(n & 0xFF) + result 
...  n >>= 8 
... 
>>> result 
'\x01\x00\x03' 
>>> import sys; sys.byteorder 
'little' 
>>> 
+0

Dies setzt voraus, dass 1 Byte 8 Bits entspricht. Ich weiß nicht, ob Sie diese Annahme in Bezug auf die Python-Semantik machen können. Die zweite Methode geht davon aus, dass die ganze Zahl bereits in Big-Endian ist. –

+1

@ott: Es ist ziemlich sicher zu sagen, dass 1 Byte 8 Bits entspricht, und Python Integer selbst haben keine Endianess - es ist nur ein Problem, wie sie gespeichert oder übertragen werden (dh es ist nur ein Problem, wenn Sie falsch ausgepackt haben) n 'von irgendwo bevor man so weit kommt). Beide Methoden sehen gut aus. –

+0

Eigentlich geht es nur davon aus, dass ein Byte * mindestens * 8 Bit hat, was vom C-Standard und damit vom Typ C PyBytes garantiert wird. – dan04

1

Eine Lösung mit struct und itertools:

>>> import itertools, struct 
>>> "".join(itertools.dropwhile(lambda c: not(ord(c)), struct.pack(">i", 456))) or chr(0) 
'\x01\xc8' 

Wir itertools durch die Verwendung eines einfachen String Streifen fallen kann:

>>> struct.pack(">i", 456).lstrip(chr(0)) or chr(0) 
'\x01\xc8' 

Oder sogar Drop struct mit einer rekursiven Funktion:

def to_bytes(n): 
    return ([chr(n & 255)] + to_bytes(n >> 8) if n > 0 else []) 

"".join(reversed(to_bytes(456))) or chr(0) 
+0

Die Methode 'struct.pack' funktioniert nicht, weil' struct.unpack' eine feste Länge benötigt. Für die anderen Methoden benötigen Sie auch eine Umkehrfunktion (trivial). –

0

ich umformuliert John Machins zweite Antwort in einer Linie für den Einsatz auf meinem Server:

def bytestring(n): 
    return ''.join([chr((n>>(i*8))&0xFF) for i in range(n.bit_length()/8,-1,-1)]) 

Ich habe festgestellt, dass die zweite Methode, Bit-Shifting verwenden, für große und kleine Zahlen schneller war, und nicht nur kleine Zahlen.

+0

Ich bekomme einen Fehler bei der Verwendung dieser mit großen Ganzzahlen. z.B. big = 2442323423424323434242335353 => TypeError: 'float' Objekt kann nicht als Integer interpretiert werden – bjmc

Verwandte Themen