Verhalten von pack() bei 32-Bit Zahlen
Gerold
- php
Hi,
ich möchte lange Binärstrings mit ...
$ergebnis_array = unpack( 'N*', $langer_binaerstring); // Big. Endian
$ergebnis_array = unpack( 'V*', $langer_binaerstring); // Lit. Endian
... in 32-Bit Integer zerlegen und umwandeln. Dabei bin ich auf folgendes Problem gestossen:
In der Doku http://de3.php.net/manual/de/function.pack.php heisst es zu den pack() Parametern 'N' und 'V':
N vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big Endian)
V vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Little Endian)
(im engl. Handbuch steht das gleiche)
Bei beiden Parametern soll pack() demnach den 32-Bit Übergabewert als positiven Integer interpretieren. Anders formuliert: das Ergebnis von pack() dürfte niemals negativ sein.
Nach allen Test die ich gemacht habe ist das aber falsch. Bei Verwendung von Paramter 'N' gibt Bit Nr. 31 (also das 32ste Bit von rechts) an, ob die Zahl positiv oder negativ ist. Also ein typisches Zweierkomplement.
Wenn ich nun keinen grossen Denkfehler gemacht habe, das ist entweder die Dokumentation oder der PHP-Interpreter fehlerhaft.
Weiss jemand, ob die Doku falsch ist oder der Interpreter (dritte Variante siehe direkt hier drüber)? Ich Frage deshalb, weil ich sicher sein will dass mein Programm auch in ein paar Jahren noch läuft. Wenn es ein Interpreterfehler ist, muss ich damit rechnen, das der Fehler bei der nächsten Version behoben ist.
Andere Parameter von pack() für 32-Bit-Zahlen ('I','L') helfen mir nicht weiter - ich möchte unbedingt maschinen- bzw. prozessorunabhängig sein.
Unten noch ein kurzes Programm, dass das Verhalten von pack() demonstriert.
Vielen Dank, Gerold
PS: ich schaue mind. 2 Tage nach Antworten rein.
#!/usr/bin/php
<?php
// Diese Programm laeuft in Unix Shellumgebung und testet die PHP Funktion
// pack() mit dem Parameter 'N' bzw. 'V'.
function unpack_test($B1, $B2, $B3, $B4)
{
// Unsere 4 Bytes mit Parameter 'N' und 'V' an unpack() uebergeben und das
// Ergebnis in $$unpack_N bzw. $unpack_V speichern.
$unpack_N = unpack( 'N', $B1.$B2.$B3.$B4); // Reihenfolge der Bytes 1,2.3,4
$unpack_V = unpack( 'V', $B4.$B3.$B2.$B1); // Reihenfolge der Bytes 4,3,2,1
// Vorbereitung der Ausgabe: Strings zur Binaerdarstellung (mit 1en und 0en)
// generieren. Die Strings werden unten mit echo ausgegeben.
$printf_format = '%1$08b %2$08b %3$08b %4$08b';
$binaer_str_N = sprintf($printf_format, ord($B1), ord($B2),ord($B3), ord($B4) );
$binaer_str_V = sprintf($printf_format, ord($B4), ord($B3),ord($B2), ord($B1) );
// Ausgabe der Daten von unpack()
echo "$binaer_str_N entspr. in unpack_N $unpack_N[1] \n";
// echo "$binaer_str_V entspr. in unpack_V $unpack_V[1] \n";
// Wenn die Zeile fuer die Ausgabe der "V Werte" direkt hier drueber einkom-
// mentiert wird, wird die Ausgabe etwas unuebersichtlich.
}
// Aufruf der Funktion unpack_test() mit verschiedenen Uebergabewerten.
echo "Die kleinsten negativen Zahlen ... \n";
unpack_test( chr(128), chr( 0), chr( 0), chr( 0) );
unpack_test( chr(128), chr( 0), chr( 0), chr( 1) );
unpack_test( chr(128), chr( 0), chr( 0), chr( 2) );
unpack_test( chr(128), chr( 0), chr( 0), chr( 3) );
echo "Die groessten negativen Zahlen ... \n";
unpack_test( chr(255), chr(255), chr(255), chr(252) );
unpack_test( chr(255), chr(255), chr(255), chr(253) );
unpack_test( chr(255), chr(255), chr(255), chr(254) );
unpack_test( chr(255), chr(255), chr(255), chr(255) );
echo "Die kleinsten positiven Zahlen ... \n";
unpack_test( chr( 0), chr( 0), chr( 0), chr( 0) );
unpack_test( chr( 0), chr( 0), chr( 0), chr( 1) );
unpack_test( chr( 0), chr( 0), chr( 0), chr( 2) );
unpack_test( chr( 0), chr( 0), chr( 0), chr( 3) );
echo "Die groessten positiven Zahlen ... \n";
unpack_test( chr(127), chr(255), chr(255), chr(252) );
unpack_test( chr(127), chr(255), chr(255), chr(253) );
unpack_test( chr(127), chr(255), chr(255), chr(254) );
unpack_test( chr(127), chr(255), chr(255), chr(255) );
echo "... \n";
die(); // Keine Ausgabe hinter "?>"
?>
moin,
N vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Big Endian)
V vorzeichenloser Long-Typ (immer 32 Bit, Byte-Folge Little Endian)
(im engl. Handbuch steht das gleiche)
das ist ja wie in Perl ;)
Bei beiden Parametern soll pack() demnach den 32-Bit Übergabewert als positiven Integer interpretieren. Anders formuliert: das Ergebnis von pack() dürfte niemals negativ sein.
Isses auch nicht:
pack() erzeugt eine Bitfolge(32 Bit, 4 byte), da gibt es weder "postitiv" noch "negativ". Der Unterschied zwischen Schablone V und N besteht lediglich darin, dass die 4 bytes eine andere Reihenfolge haben (V_ax Order, N_etwork Order).
Die einzelnen bytes können Wertigkeiten von 0..255 annehmen. Wenn die Wertigkeit 45 ist, sieht das byte aus wie ein Minuszeichen (sofern Du mit einem Textbetrachter guckst oder das byte auf der Konsole ausgibst).
Hotti
Hello,
Nach allen Test die ich gemacht habe ist das aber falsch. Bei Verwendung von Paramter 'N' gibt Bit Nr. 31 (also das 32ste Bit von rechts) an, ob die Zahl positiv oder negativ ist. Also ein typisches Zweierkomplement.
Wenn ich nun keinen grossen Denkfehler gemacht habe, das ist entweder die Dokumentation oder der PHP-Interpreter fehlerhaft.
Du hast die Systemgrenzen vergessen. Auf was für einem System fährst Du dein PHP und für welches wurde es übersetzt? Hat es 32 oder 64 Bit? PHP nimmt hier leider selber keine Rücksicht auf "vorzeichenlos" oder "vorzeichenbehaftet"
Siehe PHP Datentypen:
http://www.ulf-wendel.de/schulung/core/variablen_skalar.php#integer
http://de2.php.net/manual/en/language.types.integer.php
Liebe Grüße aus dem schönen Oberharz
Tom vom Berg
hi,
bischen ausführlicher, heute morgen war meine Zeit knapp ;)
Beispiele in Perl, dürfte auch mit PHP genauso funktionieren:
my @liste = unpack "N", "WORD"; # Erzeuge genau einen Integer aus 4 Byte
print "@liste\n"; # sehe die Zahl 1464816196
# Nehme die Zahl 1464816196 und erzeuge die 4 Bytes:
print pack("N", 1464816196), "\n"; # sehe "WORD"
# Nehme die Zahl und Schablone "V":
print pack("V", 1464816196), "\n"; # sehe "DROW"
# Unterschied ist klar, V dreht die Bytes rum.
Die Schablone "N" oder "V" passt also genau auf 4 byte, 32 bit. Hastu
my @liste = unpack "NN", "WORDWORD"; # Erzeuge genau zwei Integer aus 8 Byte
print "@liste\n"; # siehst Du 1464816196 1464816196
Beachte, dass Bytes nicht immer als Text lesbar sind, aber "WORD" ist ein schönes Beispiel ;)
Der Vollständigkeit halber nehmen wir mal eine andere Schablone:
my @liste = unpack "CCCC", "WORD"; # Erzeuge eine Liste
print "@liste\n"; # siehst Du: 87 79 82 68
Schablone "C" steht im Gegensatz zu "N" nicht für 4 byte, sondern für 1 byte. Die Umkehrung von pack() zu unpack() ist nun hoffentlich verständlich.
Anmerkung: Schablone "N" oder "V" erzeugt aus _jeder_ Zahl, egal ob 1 oder 99 oder 125343 stets genau 4 byte. Dies ist die Grundlage dafür, Datenstrukturen in Binärsequenzen zu transformieren und umgekehrt: Binärdateien zu lesen. In der letzten Ausgabe des Perl-Magazin "Foo" gibt es dazu einen Artikel von mir,
Viele Grüße,
Rolf Rost (Hotti)
Hallo,
mein Nachtrag:
* ich hatte es vieleicht in der Problemdarstellung nicht klar ausgedrückt: es ging um das Verhalten von "unpack()", nicht von "pack".
* @hotte: ja, die Funktion "unpack()" ist von Perl ausgeliehen.
* Habe vor der Fragestellung einen wichigen Hinweis der der PHP-Doku zu unpack (nicht zu pack) übersehen. Der Text:
Achtung
Beachten Sie, dass PHP Integer-Werte intern mit Vorzeichen speichert.
Wenn Sie einen großen vorzeichenlosen Longwert entpacken und er von
der selben Größe ist, die PHP für dessen Speicherung verwendet, wird
das Ergebnis ein negativer Wert sein (auch wenn Sie dieses als
vorzeichenlos zu entpacken angegeben haben).
Letztendlich ist das Problem wohl philiosophisch. Die Funktion unpack() kann zwar positive Zahlen groesser 2^15 (bis 2^16) darstellen. Da die Funktion unpack() aber immer in mit einem Array von Interger antwortet, und das Longinteger-Intervall von PHP von minus 2^15 bis plus 2^15 geht, passiert der Fehler beim auswerten des Arrays.
Das passt auch zu dem Datentyp-Hinweis von Tom.
Mit dem Verhalten von unpack() kann ich leben wenn es stabil ist !!! Stabil meint Prozessorunabhängig (ich will mein Prog. bei verschiednen Webhostern ablegen) und auch in zukünftigen PHP-Versionen so bleibt.
Danke für Hilfe (ich schau nochmal rein falls jemand noch was schreibt), Gerold