L'arithmétique binaire " / " maj droite "un shr b" avec des nombres entiers signés que stockées dans des variables – de mauvais résultats! L'insecte interne de Delphi?

j'ai une question (ou plus probablement un rapport de bogue) sur le comportement de bit shifting dans Delphi (testé dans Borland Delphi 7).

cible: effectuer un déplacement" arithmétique " bitwise right avec n'importe quel nombre.

cela signifie que le signe-bit doit être étendu – le nombre binaire sera rempli à partir de la gauche avec 1 au lieu de 0 si le bit le plus significatif d'un nombre a été défini.

donc, nombre " -1 " après un décalage arithmétique à droite doit rester le même nombre( tous les bits = 1), mais avec "logical shift" (qui remplissent toujours le nombre avec des zéros) doit donner un entier positif maximal (positif maximal signé entier, pour être correct)

Je l'ai testé seulement sur le système 32 bits (Windows); de plus, j'ai besoin qu'il fonctionne explicitement avec des entiers 32 bits.

ressemble à un bug interne dans Delphi avec" shr " quand le numéro de source est stocké dans une variable.

mon exemple de code:

program bug;

{$APPTYPE CONSOLE}

var
I:Integer;
C:Cardinal;

begin
I := -1;  // we’ll need that later
C := $FFFFFFFF;

(ce n'est que le début). Ensuite, essayons quelques "shr"s:

Writeln('0) ', -1 shr 1 );
Writeln('1) ', $FFFFFFFF shr 1 );

" -1 "est l'équivalent signé de"$ffffff". Il semble que le comportement " shr " (arithmétique ou logique) est basé sur le fait que le numéro source soit signé ou non (entier ou cardinal).

sortie:

0) -1
1) 2147483647

tout à fait correct. Alors je dois essayer de lancer manuellement ces nombres à des entiers ou cardinaux:

Writeln('2) ', Integer(-1) shr 1 );
Writeln('3) ', Integer($FFFFFFFF) shr 1 );
Writeln('4) ', Cardinal(-1) shr 1 );
Writeln('5) ', Cardinal($FFFFFFFF) shr 1 );

résultat:

2) -1
3) -1
4) 2147483647
5) 2147483647

toujours correct. Donc, je pense que je peux jeter ce "integer" si j'ai besoin d'un décalage; ou cast de "cardinal" quand je veux un décalage logique. Mais attendez! Exemple avec les variables (déclarées ci-dessus):

Writeln('6) ', I shr 1 );
Writeln('7) ', C shr 1 );

soudainement:

6) 2147483647
7) 2147483647

INCORRECT. Mon "je" était un entier signé, et je attendu le décalage! Alors, peut-être coulée pourrait aider?

Writeln('8) ', Integer(I) shr 1 );
Writeln('9) ', Cardinal(I) shr 1 );
Writeln('A) ', Integer(C) shr 1 );
Writeln('B) ', Cardinal(C) shr 1 );

Non, toujours le même...

8) 2147483647
9) 2147483647
A) 2147483647
B) 2147483647

les choses sont encore pires si je vais essayer de faire une fonction "a shr b" et l'utiliser à la place:

// Simple shift right with signed integers
function shrI(a,b:Integer):Integer;
begin
Result := a shr b;
end;

// Simple shift right with unsigned integers
function shrC(a,b:Cardinal):Cardinal;
begin
Result := a shr b;
end;

Maintenant:

Writeln('C) ', shrI(-1,1) );
Writeln('D) ', shrC($FFFFFFFF,1) );
"1519230920 – - il a cessé de fonctionner même avec des expressions constantes: (cela a du sens, parce que les nombres sont à nouveau stockés dans des variables à l'intérieur d'une fonction)

C) 2147483647
D) 2147483647

puisque j'ai besoin de faire mon déplacement arithmétique correct de toute façon, j'ai écrit ces formules pour le faire (déplacement "a" à droite par "B" bits). Le premier est le changement logique:

(a shr b) and ((1 shl (32-b))-1)

j'ai juste besoin de bitwise-et le résultat avec "32 - b" ceux (de la droite) pour effacer "b" bits de gauche dans le cas "shr" me manquer et a fait le déplacement arithmétique à la place (aucun exemple ne montre ceci, mais juste pour être sûr). Puis le décalage arithmétique:

(a shr b) or (( 0-((a shr 31) and 1)) shl (32-b))

I besoin de bitwise-ou le résultat avec" b " ceux à partir de la gauche, mais seulement quand le bit le plus significatif a été défini; pour faire ceci tout d'abord je prends le bit de signe avec "(un shr 31) et 1", Puis je nie ce nombre pour obtenir "-1" (ou $FFFFFFFF – tous les bits =1) si source était négative, et 0 autrement (j'ai mis "0-x" au lieu de juste "-x" parce que dans mon C-port dans certains cas bcc32 C-compilateur signaler un avertissement de lier pour nier un entier non signé); et finalement je l'ai déplacé à" 32 - b "bits gauche, donc j'ai eu ce que je souhaite même quand" shr" echoue et donne des zéros. J'ai fait deux versions de chaque fonction pour traiter à la fois des entiers et des cardinaux (aussi je pourrais partager des noms et les "surcharger" pour moi, mais ici Je ne le ferai pas pour garder l'exemple clair):

// Logical shift right with signed integers
function srlI(a,b:Integer):Integer;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

// Arithmetic shift right with signed integers
function sraI(a,b:Integer):Integer;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

// Logical shift right with unsigned integers
function srlC(a,b:Cardinal):Cardinal;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

// Arithmetic shift right with unsigned integers
function sraC(a,b:Cardinal):Cardinal;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

Test:

Writeln('E) ', sraI(-1,1) );
Writeln('F) ', srlI(-1,1) );
Writeln('G) ', sraC($FFFFFFFF,1) );
Writeln('H) ', srlC($FFFFFFFF,1) );

et obtenu des résultats parfaits:

E) -1
F) 2147483647
G) 4294967295
H) 2147483647

(G-case est toujours correcte, parce que "4294967295" est la version non signée de "-1")

contrôles Finals avec variables:

Writeln('K) ', sraI(I,1) );
Writeln('L) ', srlI(I,1) );
Writeln('M) ', sraC(C,1) );
Writeln('N) ', srlC(C,1) );

Parfait:

K) -1
L) 2147483647
M) 4294967295
N) 2147483647

pour ce bug, j'ai aussi essayé de changer le second nombre (quantité de décalage) en une variable et/ou d'essayer un autre casting – même bug présent, on dirait qu'il n'est pas lié au second argument. Et essayer de lancer le résultat (à l'entier ou au cardinal) avant la sortie n'a rien amélioré non plus.

pour être sûr que je ne suis pas seulement celui qui a le bug, j'ai essayé pour exécuter mon exemple entier à http://codeforces.com / (un utilisateur enregistré peut compiler et exécuter un morceau de code dans différentes langues et compilateurs côté serveur) pour voir la sortie.

"Delphi 7" compilateur m'a donné exactement ce que j'ai – bug était présent. Option Alternative, "Free Pascal 2" affiche encore plus de sortie erronée:

0) 9223372036854775807
1) 2147483647
2) 9223372036854775807
3) 9223372036854775807
4) 2147483647
5) 2147483647
6) 2147483647
7) 2147483647
8) 2147483647
9) 2147483647
A) 2147483647
B) 2147483647
C) 2147483647
D) 2147483647
E) -1
F) 2147483647
G) 4294967295
H) 2147483647
K) -1
L) 2147483647
M) 4294967295
N) 2147483647

étrange "9223372036854775807" dans les cas 0-2-3 (il y avait "-1", " entier (-1)" et "Integer($FFFFFFFF)" qui ne se souviennent pas).

voici tout mon exemple dans Delphi:

program bug;

{$APPTYPE CONSOLE}

// Simple shift right with signed integers
function shrI(a,b:Integer):Integer;
begin
Result := a shr b;
end;

// Simple shift right with unsigned integers
function shrC(a,b:Cardinal):Cardinal;
begin
Result := a shr b;
end;

// Logical shift right with signed integers
function srlI(a,b:Integer):Integer;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

// Arithmetic shift right with signed integers
function sraI(a,b:Integer):Integer;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

// Logical shift right with unsigned integers
function srlC(a,b:Cardinal):Cardinal;
begin
Result := (a shr b) and ((1 shl (32-b))-1);
end;

// Arithmetic shift right with unsigned integers
function sraC(a,b:Cardinal):Cardinal;
begin
Result := (a shr b) or (( 0-((a shr 31) and 1)) shl (32-b));
end;

var
I:Integer;
C:Cardinal;

begin
I := -1;
C := $FFFFFFFF;

Writeln('0) ', -1 shr 1 );
Writeln('1) ', $FFFFFFFF shr 1 );
// 0) -1           - correct
// 1) 2147483647   - correct

Writeln('2) ', Integer(-1) shr 1 );
Writeln('3) ', Integer($FFFFFFFF) shr 1 );
// 2) -1           - correct
// 3) -1           - correct

Writeln('4) ', Cardinal(-1) shr 1 );
Writeln('5) ', Cardinal($FFFFFFFF) shr 1 );
// 4) 2147483647   - correct
// 5) 2147483647   - correct

Writeln('6) ', I shr 1 );
Writeln('7) ', C shr 1 );
// 6) 2147483647   - INCORRECT!
// 7) 2147483647   - correct

Writeln('8) ', Integer(I) shr 1 );
Writeln('9) ', Cardinal(I) shr 1 );
// 8) 2147483647   - INCORRECT!
// 9) 2147483647   - correct

Writeln('A) ', Integer(C) shr 1 );
Writeln('B) ', Cardinal(C) shr 1 );
// A) 2147483647   - INCORRECT!
// B) 2147483647   - correct

Writeln('C) ', shrI(-1,1) );
Writeln('D) ', shrC($FFFFFFFF,1) );
// C) 2147483647   - INCORRECT!
// D) 2147483647   - correct

Writeln('E) ', sraI(-1,1) );
Writeln('F) ', srlI(-1,1) );
// E) -1           - correct
// F) 2147483647   - correct

Writeln('G) ', sraC($FFFFFFFF,1) );
Writeln('H) ', srlC($FFFFFFFF,1) );
// G) 4294967295   - correct
// H) 2147483647   - correct

Writeln('K) ', sraI(I,1) );
Writeln('L) ', srlI(I,1) );
// K) -1           - correct
// L) 2147483647   - correct

Writeln('M) ', sraC(C,1) );
Writeln('N) ', srlC(C,1) );
// M) 4294967295   - correct
// N) 2147483647   - correct

end.

alors j'étais curios, est-ce que ce bug est aussi présent en C++? J'ai écrit un port en C++ et utilise (Borland!) bcc32.exe compiler.

Résultats:

0) -1
1) 2147483647
2) -1
3) -1
4) 2147483647
5) 2147483647
6) -1
7) 2147483647
8) -1
9) 2147483647
A) -1
B) 2147483647
C) -1
D) 2147483647
E) -1
F) 2147483647
G) 4294967295
H) 2147483647
K) -1
L) 2147483647
M) 4294967295
N) 2147483647

tout va bien. Voici la version C++, au cas où quelqu'un voudrait aussi regarder:

#include <iostream>
using namespace std;

// Simple shift right with signed integers
int shrI(int a, int b){
return a >> b;
}

// Simple shift right with unsigned integers
unsigned int shrC(unsigned int a, unsigned int b){
return a >> b;
}

// Logical shift right with signed integers
int srlI(int a, int b){
return (a >> b) & ((1 << (32-b))-1);
}

// Arithmetic shift right with signed integers
int sraI(int a, int b){
return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b));
}

// Logical shift right with unsigned integers
unsigned int srlC(unsigned int a, unsigned int b){
return (a >> b) & ((1 << (32-b))-1);
}

// Arithmetic shift right with unsigned integers
unsigned int sraC(unsigned int a, unsigned int b){
return (a >> b) | (( 0-((a >> 31) & 1)) << (32-b));
}

int I;
unsigned int C;

int main(){
I = -1;
C = 0xFFFFFFFF;

cout<<"0) "<<( -1 >> 1 )<<endl;
cout<<"1) "<<( 0xFFFFFFFF >> 1 )<<endl;
// 0) -1           - correct
// 1) 2147483647   - correct

cout<<"2) "<<( ((int)(-1)) >> 1 )<<endl;
cout<<"3) "<<( ((int)(0xFFFFFFFF)) >> 1 )<<endl;
// 2) -1           - correct
// 3) -1           - correct

cout<<"4) "<<( ((unsigned int)(-1)) >> 1 )<<endl;
cout<<"5) "<<( ((unsigned int)(0xFFFFFFFF)) >> 1 )<<endl;
// 4) 2147483647   - correct
// 5) 2147483647   - correct

cout<<"6) "<<( I >> 1 )<<endl;
cout<<"7) "<<( C >> 1 )<<endl;
// 6) -1           - correct
// 7) 2147483647   - correct

cout<<"8) "<<( ((int)(I)) >> 1 )<<endl;
cout<<"9) "<<( ((unsigned int)(I)) >> 1 )<<endl;
// 8) -1           - correct
// 9) 2147483647   - correct

cout<<"A) "<<( ((int)(C)) >> 1 )<<endl;
cout<<"B) "<<( ((unsigned int)(C)) >> 1 )<<endl;
// A) -1           - correct
// B) 2147483647   - correct

cout<<"C) "<<( shrI(-1,1) )<<endl;
cout<<"D) "<<( shrC(0xFFFFFFFF,1) )<<endl;
// C) -1           - correct
// D) 2147483647   - correct

cout<<"E) "<<( sraI(-1,1) )<<endl;
cout<<"F) "<<( srlI(-1,1) )<<endl;
// E) -1           - correct
// F) 2147483647   - correct

cout<<"G) "<<( sraC(0xFFFFFFFF,1) )<<endl;
cout<<"H) "<<( srlC(0xFFFFFFFF,1) )<<endl;
// G) 4294967295   - correct
// H) 2147483647   - correct

cout<<"K) "<<( sraI(I,1) )<<endl;
cout<<"L) "<<( srlI(I,1) )<<endl;
// K) -1           - correct
// L) 2147483647   - correct

cout<<"M) "<<( sraC(C,1) )<<endl;
cout<<"N) "<<( srlC(C,1) )<<endl;
// M) 4294967295   - correct
// N) 2147483647   - correct

}

avant de poster ici, j'ai essayé de chercher ce problème, et je n'ai trouvé aucune mention de ce bug. J'ai aussi regardé ici: Quel est le comportement de shl et shr pour les opérandes de taille non registre? et ici: déplacement arithmétique à droite plutôt qu'à droite déplacement logique – mais il y a d'autres problèmes ont été discutés (que le compilateur interne jette n'importe quel type à 32 bits avant de faire le déplacement réel; ou déplacement de plus de 31 bits), mais pas le bogue de la mine.

Mais attendez, voici mon problème: http://galfar.vevb.net/wp/2009/shift-right-delphi-vs-c / !

avec une remarque: ils disent –

à Delphi, la SHR est toujours une opération de la SHR: elle ne tient jamais compte du signal.

mais mon exemple montre que Delphi ne prendre signe en compte, mais seulement lorsque le nombre de source est une expression constante, pas variable. Donc "-10 shr 2" égale à "-3", mais" x shr 2 "égale à" 1073741821 "quand"x:=-10".

donc je pense que c'est un bug, et pas un" comportement "que" shr " est toujours logique. Vous voyez, pas toujours.

Essayer d'activer/désactiver toutes les options du compilateur telles qu'une vérification de la portée ou des optimisations n'a rien changé.

aussi, ici j'ai posté des exemples de la façon de contourner ce problème et avoir le déplacement arithmétique correct à droite. Et mon la question principale est: Ai-je raison?

semble que le décalage de gauche est toujours bon dans Delphi (il n'utilise jamais le bit de signe original, et pas "non défini": pour les entiers signés il se comporte comme un casting à cardinal avant de déplacer et de casting le résultat à nouveau à entier – un nombre peut soudainement devenu négatif bien sûr). Mais maintenant je me demande, d'autres bugs similaires en Delphi? C'est le premier bogue vraiment important que je découvre dans Delphi 7. J'aime Delphi plus que C++ exactement parce que j'étais toujours sûr que mon code est à chaque fois que je fais ce que je veux, sans déboguer-tester chaque nouveau morceau inhabituel de code que je suis sur le point d'écrire (IMHO).

P.S. voici quelques liens utiles que StackOverflow system me suggère lorsque j'ai tapé mon titre avant de poster cette question. Encore une fois, des informations intéressantes, mais pas à propos de ce bug:

Arithmétique de décalage de bit sur un entier signé

décalage à droite signé = résultat étrange?

opérateurs de postes en bits sur Types signés

devez-vous toujours utiliser " int " pour les nombres en C, même s'ils ne sont pas négatifs?

les résultats des opérations bitwise sur les entiers signés sont-ils définis?

vérifier que c / c++ a signé décalage à droite en arithmétique pour un compilateur?

émuler le bit-shift variable en n'utilisant que des déplacements constants?

P. P. S. Merci beaucoup à Stack Exchange Team pour l'aide dans l'affichage de cet article. Les gars, vous êtes les meilleurs!

26
demandé sur Community 2015-08-24 21:45:37

3 réponses

il y a un bug, mais ce n'est pas ce que vous pensez. Voici la documentation pour shr :

Si x est un entier négatif, les opérations shl et shr sont indiquées clairement dans l'exemple suivant:

var
  x: integer;
  y: string;

...
begin
  x := -20;
  x := x shr 1;
  //As the number is shifted to the right by 1 bit, the sign bit's value replaced is
  //with 0 (all negative numbers have the sign bit set to 1). 

  y := IntToHex(x, 8);
  writeln(y);
  //Therefore, x is positive.
  //Decimal value: 2147483638
  //Hexadecimal value: 7FFFFFF6
  //Binary value: 0111 1111 1111 1111 1111 1111 1111 0110
end.

, shr et shl sont toujours décalage logique et ne sont pas en décalage.

le défaut est en fait dans la manipulation de constantes vraies négatives:

Writeln('0) ', -1 shr 1 );

ici, -1 est une valeur signée. Il a en fait le type Shortint , un entier 8 bits signé. Mais les opérateurs de décalage de fonctionner sur 32 bits, donc c'est signe étendu à une valeur 32 bits. Cela signifie donc que cet extrait devrait produire deux lignes avec une sortie identique:

var
  i: Integer;
....
i := -1;
Writeln(-1 shr 1);
Writeln( i shr 1);

et que la sortie devrait être:

2147483647
2147483647

sur les versions modernes de Delphes, certainement de la version 2010 et plus tard, mais peut-être même des versions plus anciennes, c'est le cas.

mais selon votre question, dans Delphi 7, -1 shr 1 évalue à -1 ce qui est faux, parce que shr est déplacement logique.

Nous pouvons deviner la source du défaut. Le compilateur évalue -1 shr 1 parce qu'il s'agit d'une valeur constante, et le compilateur ne le fait tout simplement pas correctement, en utilisant un décalage arithmétique au lieu d'un décalage logique.

incidemment, la documentation contient une autre erreur. Il dit:

les opérations x shl y et x shr y déplacent la valeur de x vers la gauche ou la droite par des bits y, ce qui (si x est un entier non signé) équivaut à multiplier ou diviser x par 2^y; le résultat est du même type que x.

La dernière partie n'est pas vrai. L'expression x shl y est un type à 32 bits, si x est un type à 8 bits., Type 16 ou 32 bits, un type 64 bits autrement.


puisque votre but réel est de mettre en œuvre le décalage arithmétique, alors rien de tout cela ne vous importe. Vous ne pouvez pas utiliser shl ou shr . Vous devrez mettre en œuvre le changement arithmétique vous-même. Je suggère que vous le fassiez en utilisant inline assembler car je soupçonne que cela pourrait être finalement plus facile à lire et à vérifier.

18
répondu David Heffernan 2015-08-24 21:26:08

si vous êtes coincé avec les versions asm des changements arithmétiques, voici un peu de code qui fonctionnerait:

notez que selon: http://docwiki.embarcadero.com/RADStudio/XE8/en/Program_Control

Les 3 premiers paramètres sont passés dans les registres comme suit: EAX, EDX, ECX

en 64 bits l'ordre de Registre est: RCX, RDX, R8, R9 151920920"

le résultat des fonctions est passé en EAX

unit SARL;

interface

  function sar(const base: integer; shift: byte): integer; 
  function sal(const base: integer; shift: byte): integer;

implementation

  function sar(const base: integer; shift: byte): integer;
  asm
    {$IFDEF CPU64BIT}
    mov eax,ecx
    mov ecx,edx
    sar eax,cl
    {$ELSE}
    mov ecx,edx
    sar eax,cl  //shr is very different from sar
    {$ENDIF}
  end;

  function sal(const base: integer; shift: byte): integer; 
  asm
    {$IFDEF CPU64BIT}
    mov eax,ecx
    mov ecx,edx
    shl eax,cl
    {$ELSE}
    mov ecx,edx
    shl eax,cl   //Note that sal and shl are the same thing.
    {$ENDIF}
  end;

end.
3
répondu Johan 2015-08-26 19:51:01

j'ai testé dans Delphi 7 et il semble que le simple fait d'utiliser" div 2 " sur une variable entière compile directement à une opération d'assemblage SAR (comme vu dans la fenêtre CPU).

mise à jour: Div ne fonctionne pas correctement comme un remplacement pour la R-S comme je l'ai expliqué dans mon commentaire dans cette réponse. Le compilateur génère une déclaration SAR, mais teste ensuite le bit de signe et ajuste la réponse en ajoutant le bit qui a été décalé sur la droite si le bit de signe est réglé. Cela donne à l' comportement correct pour l'opérateur div sur les nombres négatifs mais va à l'encontre de notre objectif d'obtenir le comportement SAR correct.

1
répondu Jannie Gerber 2015-10-17 15:06:16