Des limites sont fixées pour les capacités d'évaluation arithmétique du bash
shell. Le manuel est succinct sur cet aspect de l'arithmétique des coques mais déclare :
L'évaluation est effectuée dans des entiers à largeur fixe sans vérification de débordement, bien que la division par 0 soit interceptée et signalée comme une erreur. Les opérateurs et leur priorité, associativité et valeurs sont les mêmes que dans le langage C.
Le nombre entier à largeur fixe auquel il se réfère concerne vraiment le type de données utilisé (et les raisons pour lesquelles cela se situe au-delà), mais la valeur limite est exprimée /usr/include/limits.h
de cette manière:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
Et une fois que vous le savez, vous pouvez confirmer cet état de fait comme suit:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Il s'agit d'un entier de 64 bits et cela se traduit directement dans le shell dans le contexte de l'évaluation arithmétique:
# echo $(((2**63)-1)); echo $((2**63)); echo $(((2**63)+1)); echo $((2**64))
9223372036854775807 //the practical usable limit for your everyday use
-9223372036854775808 //you're that much "away" from 2^64
-9223372036854775807
0
# echo $((9223372036854775808+9223372036854775807))
-1
Ainsi, entre 2 63 et 2 64 -1, vous obtenez des entiers négatifs vous montrant à quelle distance de ULONG_MAX vous êtes 1 . Lorsque l'évaluation atteint cette limite et déborde, quel que soit l'ordre, vous ne recevez aucun avertissement et cette partie de l'évaluation est réinitialisée à 0, ce qui peut produire un comportement inhabituel avec quelque chose comme l' exponentiation associative à droite, par exemple:
echo $((6**6**6)) 0 // 6^46656 overflows to 0
echo $((6**6**6**6)) 1 // 6^(6^46656) = 6^0 = 1
echo $((6**6**6**6**6)) 6 // 6^(6(6^46656)) = 6^(6^0) = 6^1
echo $((6**6**6**6**6**6)) 46656 // 6^(6^(6^(6^46656))) = 6^6
echo $((6**6**6**6**6**6**6)) 0 // = 6^6^6^1 = 0
...
L'utilisation sh -c 'command'
ne change rien, je dois donc supposer qu'il s'agit d'une sortie normale et conforme. Maintenant que je pense avoir une compréhension basique mais concrète de la plage et de la limite arithmétiques et de ce que cela signifie dans le shell pour l'évaluation de l'expression, j'ai pensé que je pouvais rapidement jeter un œil aux types de données utilisés par les autres logiciels sous Linux. J'ai utilisé quelques bash
sources que je devais compléter l'entrée de cette commande:
{ shopt -s globstar; for i in /path/to/source_bash-4.2/include/**/*.h /usr/include/**/*.h; do grep -HE '\b(([UL])|(UL)|())LONG|\bFLOAT|\bDOUBLE|\bINT' $i; done; } | grep -iE 'bash.*max'
bash-4.2/include/typemax.h:# define LLONG_MAX TYPE_MAXIMUM(long long int)
bash-4.2/include/typemax.h:# define ULLONG_MAX TYPE_MAXIMUM(unsigned long long int)
bash-4.2/include/typemax.h:# define INT_MAX TYPE_MAXIMUM(int)
Il y a plus de sortie avec les if
instructions et je peux rechercher une commande comme awk
aussi etc. Je remarque que l'expression régulière que j'ai utilisée ne détecte rien sur les outils de précision arbitraire que j'ai comme bc
et dc
.
Des questions
- Quelle est la raison de ne pas vous avertir (comme
awk
lors de l'évaluation de 2 ^ 1024) lorsque votre évaluation arithmétique déborde? Pourquoi les entiers négatifs entre 2 63 et 2 64 -1 sont-ils exposés à l'utilisateur final lorsqu'il évalue quelque chose? - J'ai lu quelque part qu'une certaine saveur d'UNIX peut changer interactivement ULONG_MAX? Est-ce que quelqu'un a entendu parler de ça?
- Si quelqu'un modifie arbitrairement la valeur du nombre entier non signé dans
limits.h
, puis recompilebash
, à quoi pouvons-nous nous attendre?
Remarque
1. Je voulais illustrer plus clairement ce que j'ai vu, car il s'agit de choses empiriques très simples. Ce que j'ai remarqué, c'est que:
- (a) Toute évaluation qui donne <2 ^ 63-1 est correcte
- (b) Toute évaluation qui donne => 2 ^ 63 jusqu'à 2 ^ 64 donne un entier négatif:
- La plage de cet entier est de x à y. x = -9223372036854775808 et y = 0.
Compte tenu de cela, une évaluation qui est comme (b) peut être exprimée comme 2 ^ 63-1 plus quelque chose dans x..y. Par exemple, si on nous demande littéralement d'évaluer (2 ^ 63-1) +100 002 (mais pourrait être un nombre inférieur à celui de (a)), nous obtenons -9223372036854675807. Je dis juste l'évidence, je suppose, mais cela signifie également que les deux expressions suivantes:
- (2 ^ 63-1) + 100 002 ET;
- (2 ^ 63-1) + (LLONG_MAX - {ce que le shell nous donne ((2 ^ 63-1) + 100 002), ce qui est -9223372036854675807}) eh bien, en utilisant des valeurs positives que nous avons;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100 000
sont très proches en effet. La deuxième expression est "2" à part (2 ^ 63-1) + 100 002 c'est-à-dire ce que nous évaluons. C'est ce que je veux dire par vous obtenez des entiers négatifs vous montrant à quelle distance de 2 ^ 64 vous êtes. Je veux dire avec ces entiers négatifs et la connaissance des limites, eh bien vous ne pouvez pas terminer l'évaluation dans la plage x..y dans le shell bash mais vous pouvez ailleurs - les données sont utilisables jusqu'à 2 ^ 64 dans ce sens (je pourrais ajouter sur papier ou utilisez-le en bc). Au-delà de cela, cependant, le comportement est similaire à celui de 6 ^ 6 ^ 6 car la limite est atteinte comme décrit ci-dessous dans le Q ...
bc
, par exemple:$num=$(echo 6^6^6 | bc)
. Malheureusement,bc
met en ligne des sauts, vous devez doncnum=$(echo $num | sed 's/\\\s//g')
après; si vous le faites dans une pipe, il y a de vrais caractères de nouvelle ligne, qui sont gênants avec sed, bien que celanum=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
fonctionne. Dans les deux cas , vous avez maintenant un entier qui peut être utilisé, par exemple,num2=$(echo "$num * 2" | bc)
.bc
en définissantBC_LINE_LENGTH=0
.Réponses:
Non , comment voulez - vous? Par votre propre exemple, le maximum est:
Si "débordement" signifiait "vous obtenez des entiers négatifs vous montrant à quelle distance de ULONG_MAX vous êtes", alors si nous en ajoutons un, ne devrions-nous pas obtenir -1? Mais plutôt:
Peut-être voulez-vous dire que c'est un nombre que vous pouvez ajouter
$max
pour obtenir une différence négative, car:Mais cela ne reste pas vrai en fait:
En effet, le système utilise le complément à deux pour implémenter des entiers signés. 1 La valeur résultant d'un débordement n'est pas une tentative de vous fournir une différence, une différence négative, etc. Il est littéralement le résultat de tronquer une valeur à un nombre limité de bits, puis l' avoir interprété comme deux de l'entier signé du complément . Par exemple, la raison
$(($max + 1 + $max))
apparaît comme -1 parce que la valeur la plus élevée dans le complément à deux est tous les bits définis sauf le bit le plus élevé (qui indique négatif); les ajouter ensemble signifie essentiellement porter tous les bits vers la gauche pour que vous vous retrouviez avec (si la taille était de 16 bits, et non 64):Le bit haut (signe) est maintenant défini car il a été reporté dans l'addition. Si vous en ajoutez un de plus (00000000 00000001) à cela, vous avez alors tous les bits définis , qui en complément à deux est -1.
Je pense que cela répond partiellement à la deuxième moitié de votre première question - "Pourquoi les entiers négatifs ... sont-ils exposés à l'utilisateur final?". D'abord, parce que c'est la valeur correcte selon les règles des nombres complémentaires 64 bits deux. C'est la pratique conventionnelle de la plupart des (autres) langages de programmation de haut niveau à usage général (je ne peux pas penser à un qui ne fait pas cela), donc
bash
adhère à la convention. Quelle est également la réponse à la première partie de la première question - "Quelle est la justification?": C'est la norme dans la spécification des langages de programmation.WRT la 2ème question, je n'ai pas entendu parler de systèmes qui changent interactivement ULONG_MAX.
Cela ne ferait aucune différence dans la façon dont l'arithmétique sort, car ce n'est pas une valeur arbitraire qui est utilisée pour configurer le système - c'est une valeur de commodité qui stocke une constante immuable reflétant le matériel. Par analogie, vous pouvez redéfinir c à 55 mph, mais la vitesse de la lumière sera toujours de 186 000 miles par seconde. c n'est pas un nombre utilisé pour configurer l'univers - c'est une déduction sur la nature de l'univers.
ULONG_MAX est exactement le même. Il est déduit / calculé en fonction de la nature des nombres à N bits. La modifier
limits.h
serait une très mauvaise idée si cette constante est utilisée quelque part en supposant qu'elle est censée représenter la réalité du système .Et vous ne pouvez pas changer la réalité imposée par votre matériel.
1. Je ne pense pas que cela (le moyen de représentation entière) soit réellement garanti par
bash
, car cela dépend de la bibliothèque C sous-jacente et le standard C ne le garantit pas. Cependant, c'est ce qui est utilisé sur la plupart des ordinateurs modernes normaux.la source
$max
, comme vous le décrivez. Mes points sont 1) ce n'est pas le but, 2) assurez-vous de comprendre si vous voulez le faire, 3) ce n'est pas très utile en raison de l'applicabilité très limitée, 4) selon la note de bas de page, il n'est pas réellement garanti que le système fonctionne utilisez le complément à deux. En bref, essayer d'exploiter cela dans le code du programme serait considéré comme une très mauvaise pratique. Il existe des bibliothèques / modules "en grand nombre" (pour les shells sous POSIX,bc
) - utilisez-les si vous en avez besoin.