OVH Cloud OVH Cloud

comment creer / parser un entier signe negatif sur n octets ?

6 réponses
Avatar
Thomas Harding
Bonjour,
je sèche sur un algo :)

Je voudrais, d'une part créer un entier signé (éventuellement négatif)
sur 4 octets, à partir d'un nombre décimal, et d'autre part parser
un entier signé encodé sur 4 octets pour en faire un nombre décimal
(éventuellement négatif).

J'y arrive avec un nombre positif:

##############################################
// {{{ _interpretInteger($value)
protected function _interpretInteger($value) {
// they are _signed_ integers
$negative = false;
$value_parsed = 0;

for ($i = strlen($value) ; $i > 0 ; $i --)
if ($i == strlen($value))
if (ord($value[strlen($value) - $i]) > 0x80)
$negative = true;
else
$value_parsed += pow(256,($i - 1))
* ord($value[strlen($value) - $i]);
else
$value_parsed += pow(256,($i - 1))
* ord($value[strlen($value) - $i]);

if ($negative == true)
$value_parsed = $value;

return $value_parsed;
}
// }}}
###################################################
// {{{ _integerBuild ($integer)
protected function _integerBuild ($value) {

if ($value >= 2147483648 || $value <= -2147483648) {
trigger_error(_("Values must be between \
-2147483648 and 2147483648: assuming '0'"),E_USER_WARNING);
return chr(0x00).chr(0x00).chr(0x00).chr(0x00);
}

if ($value > 0) {
$int1 = $value %0x100;
$value -= $int1;
$value /= 256;
$int2 = $value %0x100;
$value -= $int2;
$value /=256;
$int3 = $value %0x100;
$value -= $int3;
$value /= 256;
$int4 = $value;
}
//echo "$int4#$int3#$int2#$int1";
return chr($int4).chr($int3).chr($int2).chr($int1);
}
// }}}
##################################################

Mais qui peut me donner la solution pour un nombre négatif ?

Ceci n'est pas un devoir d'étudiant (j'ai passé l'âge) :)
--
Thomas Harding

6 réponses

Avatar
Olivier Miakinen

Je voudrais, d'une part créer un entier signé (éventuellement négatif)
sur 4 octets, à partir d'un nombre décimal, et d'autre part parser
un entier signé encodé sur 4 octets pour en faire un nombre décimal
(éventuellement négatif).


J'ai d'abord failli t'envoyer sur <http://fr.php.net/sscanf> mais après
lecture plus attentive je vois que ce n'est pas le cas.

J'y arrive avec un nombre positif:


À la limite, vu que les entiers positifs entre 2147483648 et 4294967295
sont représentés de façon exacte par les flottants (pourvu que tu sois
sur une machine où les flottants sont sur 64 bits ou plus), tu peux
faire les calculs pour des nombres positifs, et ajouter ou soustraire
4294967296 selon le cas.

##############################################
// {{{ _interpretInteger($value)
protected function _interpretInteger($value) {
// they are _signed_ integers
$negative = false;
$value_parsed = 0;

for ($i = strlen($value) ; $i > 0 ; $i --)


Je n'aime pas beaucoup parcourir les tableaux du plus grand indice au
plus petit, mais bon, c'est ton choix. Vu comment tu fais, tu devras en
plus faire « - 1 » à chaque utilisation de l'indice. Par ailleurs, j'ai
l'impression que ta fonction suppose un ordre de rangement « little
endian », ce qui la rend un peu spécifique au type de machine.

Personnellement, je commencerais par transformer la chaîne en tableau de
quatre valeurs, ordonnées en « big endian » comme dans la plupart des
protocoles réseau.

if ($i == strlen($value))
if (ord($value[strlen($value) - $i]) > 0x80)


Tu as une erreur ici : le bit de signe est mis pour une valeur
supérieure *ou égale* à 0x80. Il vaudrait mieux utiliser un masque
binaire, ce qui serait à la fois plus clair et moins générateur d'erreurs.

http://fr.php.net/manual/fr/language.operators.bitwise.php

$negative = true;


Et là tu as une autre erreur : tu positionnes le flag $negative, mais
sans récupérer la valeur $value_parsed.

else
$value_parsed += pow(256,($i - 1))


Oh, quelle horreur ! Je me demande combien de fois l'opération flottante
pow() est plus coûteuse que les opérations binaires de décalage. Au
moins 1000 fois, je dirais. Probablement beaucoup plus.

http://fr.php.net/manual/fr/language.operators.bitwise.php

[...]

if ($negative == true)
$value_parsed = $value;


Ah ? Tous les calculs que tu as faits avec des exponentielles sont
ignorés quand le nombre est négatif, et tu copies juste le string tel
quel ? Pas étonnant que cela ne marche pas.

###################################################
// {{{ _integerBuild ($integer)
protected function _integerBuild ($value) {

if ($value >= 2147483648 || $value <= -2147483648) {


Encore une erreur d'inégalité. Ici tu refuses la valeur -2147483648, qui
est pourtant valide. Par ailleurs, ta comparaison avec +2147483648 se
fait en flottant.

if ($value > 2147483647 || $value < -2147483648) {

trigger_error(_("Values must be between
-2147483648 and 2147483648: assuming '0'"),E_USER_WARNING);


-2147483648 and 2147483647

return chr(0x00).chr(0x00).chr(0x00).chr(0x00);
}

if ($value > 0) {
$int1 = $value %0x100;


Opérateur &
http://fr.php.net/manual/fr/language.operators.bitwise.php

$value -= $int1;
$value /= 256;


Opérateur >>
http://fr.php.net/manual/fr/language.operators.bitwise.php

[...]
##################################################

Mais qui peut me donner la solution pour un nombre négatif ?


Si j'ai le temps je te donnerai une solution générale, pour les nombres
positifs comme négatifs. Sinon, et si personne d'autre ne le fait, tu
peux toujours suivre le conseil que je te donnais au début :

1) string -> integer
Utilise ta fonction pour les nombres positifs (de 0 à 4294967295),
puis soustrais 4294967296 si le résultat est supérieur ou égal à
2147483648.

2) integer -> string
Ajoute 4294967296 si le nombre est négatif, puis utilise ta fonction
pour les nombres positifs (de 0 à 4294967295).


--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)

Avatar
Marc
Thomas Harding wrote:
Bonjour,
je sèche sur un algo :)

Je voudrais, d'une part créer un entier signé (éventuellement négatif)
sur 4 octets, à partir d'un nombre décimal, et d'autre part parser
un entier signé encodé sur 4 octets pour en faire un nombre décimal
(éventuellement négatif).


ce n'est pas le travail de pack, unpack ?

Avatar
Vincent Lascaux
Je voudrais, d'une part créer un entier signé (éventuellement négatif)
sur 4 octets, à partir d'un nombre décimal, et d'autre part parser
un entier signé encodé sur 4 octets pour en faire un nombre décimal
(éventuellement négatif).

J'y arrive avec un nombre positif:


En deux ligne :

$result = unpack('lvalue', $data);
$result = $result['value'];

--
Vincent

Avatar
Thomas Harding

Bonjour,
et merci de ta réponse.

À la limite, vu que les entiers positifs entre 2147483648 et 4294967295
sont représentés de façon exacte par les flottants (pourvu que tu sois
sur une machine où les flottants sont sur 64 bits ou plus), tu peux
faire les calculs pour des nombres positifs, et ajouter ou soustraire
4294967296 selon le cas.


C'est indifféremment sur du 32 ou 64bits :-/

##############################################
// {{{ _interpretInteger($value)
protected function _interpretInteger($value) {
// they are _signed_ integers
$negative = false;
$value_parsed = 0;

for ($i = strlen($value) ; $i > 0 ; $i --)


Je n'aime pas beaucoup parcourir les tableaux du plus grand indice au
plus petit, mais bon, c'est ton choix. Vu comment tu fais, tu devras en
plus faire « - 1 » à chaque utilisation de l'indice. Par ailleurs, j'ai
l'impression que ta fonction suppose un ordre de rangement « little
endian », ce qui la rend un peu spécifique au type de machine.


c'est du big endian:

RFC 2910:

Every integer in this encoding MUST be encoded as a signed integer using
two's-complement binary encoding with big-endian format (also known as
"network order" and "most significant byte first"). The number of octets
for an integer MUST be 1, 2 or 4, depending on usage in the protocol.
Such one-octet integers, henceforth called SIGNED-BYTE, are used for the
version-number and tag fields. Such two-byte integers, henceforth called
SIGNED-SHORT are used for the operation-id, status-code and length
fields. Four byte integers, henceforth called SIGNED-INTEGER, are used
for value fields and the request-id.

Si j'ai le temps je te donnerai une solution générale, pour les nombres
positifs comme négatifs. Sinon, et si personne d'autre ne le fait, tu
peux toujours suivre le conseil que je te donnais au début :

1) string -> integer
Utilise ta fonction pour les nombres positifs (de 0 à 4294967295),
puis soustrais 4294967296 si le résultat est supérieur ou égal à
2147483648.

2) integer -> string
Ajoute 4294967296 si le nombre est négatif, puis utilise ta fonction
pour les nombres positifs (de 0 à 4294967295).


J'ai fait :
if ($initial_value < 0)
$int4 = chr($int4) | chr(0x80);
else
$int4 = chr($int4);
$value = $int4.chr($int3).chr($int2).chr($int1);

Mais je ne sais pas si c'est bon (désolé d'être mal-comprenant).

Les fonctions après correction :

// {{{ _integerBuild ($integer)
protected function _integerBuild ($value) {

if ($value >= 2147483647 || $value < -2147483648) {
trigger_error(_("Values must be between -2147483648
and 2147483647: assuming '0'"),E_USER_WARNING);
return chr(0x00).chr(0x00).chr(0x00).chr(0x00);
}

$initial_value = $value;

$int1 = $value & 0xFF;
$value -= $int1;
$value = $value >> 8;
$int2 = $value & 0xFF;
$value -= $int2;
$value = $value >> 8;
$int3 = $value & 0xFF;
$value -= $int3;
$value = $value >> 8;
$int4 = $value;

if ($initial_value < 0)
$int4 = chr($int4) | chr(0x80);
else
$int4 = chr($int4);
$value = $int4.chr($int3).chr($int2).chr($int1);

//echo ord($int4)."#$int3#$int2#$int1";
return chr($int4).chr($int3).chr($int2).chr($int1);
}
// }}}

// {{{ _interpretInteger($value)
protected function _interpretInteger($value) {

// they are _signed_ integers
$value_parsed = 0;

for ($i = strlen($value) ; $i > 0 ; $i --)
$value_parsed += (1 << (($i - 1)*8))
* ord($value[strlen($value) - $i]);
if ($value_parsed >= 2147483648)
$value_parsed -= 4294967296;
return $value_parsed;
}
// }}}
--
Thomas Harding


Avatar
Olivier Miakinen
Le 12/01/2006 22:03, Thomas Harding me répondait :

À la limite, vu que les entiers positifs entre 2147483648 et 4294967295
sont représentés de façon exacte par les flottants (pourvu que tu sois
sur une machine où les flottants sont sur 64 bits ou plus), tu peux
faire les calculs pour des nombres positifs, et ajouter ou soustraire
4294967296 selon le cas.


C'est indifféremment sur du 32 ou 64bits :-/


Bof... des flottants 32 bits en PHP, je ne suis pas sûr qu'il y ait
beaucoup d'implémentations qui aient ça. Je soupçonne bien que le
minimum soit 32 bits pour les entiers et 64 bits pour les flottants
sur les machines 32 bits, 64 bits pour les entiers sur les machines
64 bits. Cela dit, je n'ai pas vérifié.

[...]

J'ai fait :
if ($initial_value < 0)
$int4 = chr($int4) | chr(0x80);
else
$int4 = chr($int4);
$value = $int4.chr($int3).chr($int2).chr($int1);

Mais je ne sais pas si c'est bon (désolé d'être mal-comprenant).

[ suivi par des modifs de la solution compliquée du début ]


Tu n'as pas lu les articles de Marc et Vincent ? Je ne connaissais pas,
mais faire cette opération en natif et en une ou deux lignes, ça me
semble préférable à réinventer la roue.

http://fr.php.net/pack
http://fr.php.net/unpack


Sinon, si c'est pour le plaisir, le code résultant devrait ressembler à
quelque chose comme ça.

$chaine vers $entier :
$entier ord($chaine[0]) << 24 +
ord($chaine[1]) << 16 +
ord($chaine[2]) << 8 +
ord($chaine[3]);

$entier vers $chaine :
$chaine chr(($entier >> 24) & 0xff) .
chr(($entier >> 16) & 0xff) .
chr(($entier >> 8) & 0xff) .
chr($entier & 0xff);

(non testé car ça n'en vaut vraiment pas la peine àmha)

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)


Avatar
Olivier Miakinen
Le 12/01/2006 22:25, je répondais à Thomas Harding :

Sinon, si c'est pour le plaisir, le code résultant devrait ressembler à
quelque chose comme ça.

$chaine vers $entier :
$entier > ord($chaine[0]) << 24 +
ord($chaine[1]) << 16 +
ord($chaine[2]) << 8 +
ord($chaine[3]);


Remplacer les « + » par des « | » pour diminuer les risques d'ennuis
(conversion en float) -- quoique je ne suis pas sûr que ce soit nécessaire.

Bon, mais encore une fois, si c'est pour une utilisation pratique et pas
pour prouver que tu es vachement fort en programmation, autant utiliser
les fonctions natives.

--
Olivier Miakinen
Troll du plus sage chez les conviviaux : le nouveau venu, avec
son clan, s'infiltre dans les groupes de nouvelles. (3 c.)