Décalage au niveau du bit et le plus grand entier dans Bash

16

Il s'agit d'une question d'exploration, ce qui signifie que je ne sais pas exactement de quoi il s'agit, mais je pense qu'il s'agit du plus grand entier de Bash. Quoi qu'il en soit, je vais le définir ostensivement.

$ echo $((1<<8))
256

Je produis un entier en décalant un peu. Jusqu'où puis-je aller?

$ echo $((1<<80000))
1

Pas si loin, apparemment. (1 est inattendu, et je vais y revenir.) Mais,

$ echo $((1<<1022))
4611686018427387904

est toujours positif. Mais non:

$ echo $((1<<1023))
-9223372036854775808

Et un pas plus loin,

$ echo $((1<<1024))
1

Pourquoi 1? Et pourquoi ce qui suit?

$ echo $((1<<1025))
2
$ echo $((1<<1026))
4

Quelqu'un aimerait-il analyser cette série?

MISE À JOUR

Ma machine:

$ uname -a
Linux tomas-Latitude-E4200 4.4.0-47-generic #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Gilles 'SO- arrête d'être méchant'
la source
-9223372036854775808 = 0xF333333333333334. C'est un cas de bord drôle. Bien sûr, 4611686018427387904 = 0x4000000000000000. Je soupçonne que vous frappez une sorte de bouclage sur le nombre de bits à passer. Pourquoi fais-tu ça de toute façon?
un CVn
6
@ MichaelKjörling Pour le plaisir ;-p
2
@ MichaelKjörling Non, ce n'est pas le cas. -9223372036854775808 serait 0x8000000000000000. Vous avez omis le dernier chiffre lors de la vérification: -922337203685477580 serait 0xF333333333333334.
hvd

Réponses:

27

Bash utilise des intmax_tvariables pour l'arithmétique . Sur votre système, elles ont une longueur de 64 bits, donc:

$ echo $((1<<62))
4611686018427387904

lequel est

100000000000000000000000000000000000000000000000000000000000000

en binaire (1 suivi de 62 0s). Déplacer cela à nouveau:

$ echo $((1<<63))
-9223372036854775808

lequel est

1000000000000000000000000000000000000000000000000000000000000000

en binaire (63 0s), en arithmétique du complément à deux.

Pour obtenir le plus grand entier représentable, vous devez soustraire 1:

$ echo $(((1<<63)-1))
9223372036854775807

lequel est

111111111111111111111111111111111111111111111111111111111111111

en binaire.

Comme indiqué dans la réponse d' ilkkachu , le décalage prend le décalage modulo 64 sur les processeurs x86 64 bits (que ce soit avec ou ), ce qui explique le comportement que vous voyez:RCLSHL

$ echo $((1<<64))
1

est équivalent à $((1<<0)). Ainsi $((1<<1025))est $((1<<1)), $((1<<1026))est $((1<<2))...

Vous trouverez les définitions de type et les valeurs maximales dans stdint.h; sur votre système:

/* Largest integral types.  */
#if __WORDSIZE == 64
typedef long int                intmax_t;
typedef unsigned long int       uintmax_t;
#else
__extension__
typedef long long int           intmax_t;
__extension__
typedef unsigned long long int  uintmax_t;
#endif

/* Minimum for largest signed integral type.  */
# define INTMAX_MIN             (-__INT64_C(9223372036854775807)-1)
/* Maximum for largest signed integral type.  */
# define INTMAX_MAX             (__INT64_C(9223372036854775807))
Stephen Kitt
la source
1
Non, vous en avez besoin, Binary -a une priorité plus élevée que <<.
cuonglm
1
@cuonglm hein, me sert bien pour les tests sur zsh ... Merci encore!
Stephen Kitt
@cuonglm et Stephen. Eh bien, c'est un bon montage. echo $((1<<63-1))me donne 4611686018427387904.
@tomas yup, bash utilise la priorité de l'opérateur C, zsh a le sien par défaut où $((1<<63-1))est égal $(((1<<63)-1)).
Stephen Kitt
C'est bon à savoir, une bonne question et une réponse très approfondie, merci à vous à la fois Stephen Kitt et tomas.
Valentin B.
4

Du CHANGESfichier pour bash2.05b:

j. Le shell effectue maintenant l'arithmétique dans la plus grande taille entière prise en charge par la machine (intmax_t), au lieu de longue.

Sur les machines x86_64 intmax_tcorrespond aux entiers 64 bits signés. Vous obtenez donc des valeurs significatives entre -2^63et 2^63-1. En dehors de cette plage, vous obtenez simplement des enveloppements.

Satō Katsura
la source
Nitpick: entre -2^63et 2^63-1, inclus.
Animal nominal
4

Un décalage de 1024 donne un, car la quantité de décalage est effectivement prise modulo le nombre de bits (64), donc 1024 === 64 === 0, et 1025 === 65 === 1.

Décaler quelque chose d'autre qu'un a 1indique clairement qu'il ne s'agit pas d'une rotation de bits, car les bits supérieurs ne s'enroulent pas vers le bas avant que la valeur de décalage ne soit (au moins) 64:

$ printf "%x\n" $(( 5 << 63 )) $(( 5 << 64 ))
8000000000000000
5

Il se peut que ce comportement dépende du système. Le code bash lié à Stephen montre juste un décalage simple, sans vérification de la valeur de droite. Si je me souviens bien, les processeurs x86 n'utilisent que les six derniers bits de la valeur de décalage (en mode 64 bits), de sorte que le comportement peut provenir directement du langage machine. De plus, je pense que les décalages de plus que la largeur de bit ne sont pas clairement définis non plus en C ( gccavertit pour cela).

ilkkachu
la source
2

produire un entier en décalant un peu. Jusqu'où puis-je aller?

Jusqu'à ce que la représentation entière s'enroule (la valeur par défaut dans la plupart des shells).
Un entier 64 bits enveloppe généralement at 2**63 - 1.
C'est 0x7fffffffffffffffou 9223372036854775807en décembre.

Ce nombre «+1» devient négatif.

C'est la même chose que 1<<63:

$ echo "$((1<<62)) $((1<<63)) and $((1<<64))"
4611686018427387904 -9223372036854775808 and 1

Après cela, le processus se répète.

$((1<<80000)) $((1<<1022)) $((1<<1023)) $((1<<1024)) $((1<<1025)) $((1<<1026))

Le résultat dépend mod 64de la valeur de décalage [a] .

[a] Extrait de: Intel® 64 et IA-32 Architectures Software Developer's Manual: Volume 2 Le nombre est masqué à 5 bits (ou 6 bits si en mode 64 bits et REX.W est utilisé). La plage de comptage est limitée à 0 à 31 (ou 63 si le mode 64 bits et REX.W est utilisé). .

Aussi: rappelez-vous que $((1<<0))c'est1

$ for i in 80000 1022 1023 1024 1025 1026; do echo "$((i%64)) $((1<<i))"; done
 0 1
62 4611686018427387904
63 -9223372036854775808
 0 1
 1 2
 2 4

Donc, tout dépend de la façon dont le nombre est proche d'un multiple de 64.

Tester la limite:

La manière robuste de tester quel est l'entier positif (et négatif) maximum est de tester chaque bit à tour de rôle. Ses moins de 64 étapes pour la plupart des ordinateurs de toute façon, ce ne sera pas trop lent.

frapper

Tout d'abord, nous avons besoin du plus grand entier du formulaire 2^n(ensemble de 1 bit suivi de zéros). Nous pouvons le faire en décalant vers la gauche jusqu'à ce que le décalage suivant rend le nombre négatif, également appelé «bouclage»:

a=1;   while ((a>0));  do ((b=a,a<<=1))  ; done

best le résultat: la valeur avant le dernier quart de travail qui échoue à la boucle.

Ensuite, nous devons essayer chaque bit pour savoir lesquels affectent le signe de e:

c=$b;d=$b;
while ((c>>=1)); do
      ((e=d+c))
      (( e>0 )) && ((d=e))
done;
intmax=$d

Le nombre entier maximum ( intmax) résulte de la dernière valeur de d.

Du côté négatif (moins que 0), nous répétons tous les tests mais testons quand un bit peut être mis à 0 sans s'enrouler.

Voici un test complet avec impression de toutes les étapes (pour bash):

#!/bin/bash
sayit(){ printf '%020d 0x%016x\n' "$1"{,}; }
a=1;       while ((a>0)) ; do((b=a,a<<=1))              ; sayit "$a"; done
c=$b;d=$b; while((c>>=1)); do((e=d+c));((e>0))&&((d=e)) ; sayit "$d"; done;
intmax=$d
a=-1;      while ((a<0)) ; do((b=a,a<<=1))              ; sayit "$b"; done;
c=$b;d=$b; while ((c<-1)); do((c>>=1,e=d+c));((e<0))&&((d=e)); sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

sh

Traduit dans presque n'importe quel shell:

#!/bin/sh
printing=false
sayit(){ "$printing" && printf '%020d 0x%016x\n' "$1" "$1"; }
a=1;       while [ "$a" -gt 0  ];do b=$a;a=$((a<<1)); sayit "$a"; done
c=$b;d=$b; while c=$((c>>1)); [ "$c" -gt 0 ];do e=$((d+c)); [ "$e" -gt 0 ] && d=$e ; sayit "$d"; done;
intmax=$d
a=-1;      while [ "$a" -lt 0  ];do b=$a;a=$((a<<1)); sayit "$b"; done;
c=$b;d=$b; while [ "$c" -lt -1 ];do c=$((c>>1));e=$((d+c));[ "$e" -lt 0 ] && d=$e ; sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

En exécutant ce qui précède pour de nombreux shells,
tous (sauf bash 2.04 et mksh) ont accepté des valeurs allant jusqu'à ( 2**63 -1) sur cet ordinateur.

Il est intéressant de signaler que la coquille att :

$ attsh --version
version         sh (AT&T Research) 93u+ 2012-08-01

imprimé une erreur sur les valeurs de $((2^63)), pas ksh cependant.

Sorontar
la source