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 donc num=$(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 cela num=$(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éfinissant BC_LINE_LENGTH=0
.