Hay límites establecidos para las capacidades de evaluación aritmética del bash
shell. El manual es breve sobre este aspecto de la aritmética de shell pero establece :
La evaluación se realiza en enteros de ancho fijo sin verificación de desbordamiento, aunque la división por 0 queda atrapada y marcada como un error. Los operadores y su precedencia, asociatividad y valores son los mismos que en el lenguaje C.
A qué número entero de ancho fijo se refiere esto es realmente sobre qué tipo de datos se usa (y los detalles de por qué esto está más allá de esto), pero el valor límite se expresa /usr/include/limits.h
de esta manera:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
Y una vez que sepa eso, puede confirmar este estado de hecho así:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Este es un entero de 64 bits y se traduce directamente en el shell en el contexto de la evaluación aritmética:
# 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
Entonces, entre 2 63 y 2 64 -1, obtienes enteros negativos que te muestran qué tan lejos de ULONG_MAX estás 1 . Cuando la evaluación alcanza ese límite y se desborda, en cualquier orden que sea, no recibe ninguna advertencia y esa parte de la evaluación se restablece a 0, lo que puede generar un comportamiento inusual con algo como la exponenciación asociativa correcta, por ejemplo:
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
...
El uso sh -c 'command'
no cambia nada, así que debo suponer que esta es una salida normal y compatible. Ahora que creo que tengo una comprensión básica pero concreta del rango y límite aritmético y lo que significa en el shell para la evaluación de expresiones, pensé que podría echar un vistazo rápidamente a qué tipos de datos usa el otro software en Linux. Usé algunas bash
fuentes que tenía para complementar la entrada de este comando:
{ 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)
Hay más resultados con las if
declaraciones y también puedo buscar un comando como awk
etc. Noto que la expresión regular que utilicé no capta nada sobre las herramientas de precisión arbitraria que tengo como bc
y dc
.
Preguntas
- ¿Cuál es la razón para no advertirte (como
awk
cuando evalúas 2 ^ 1024) cuando tu evaluación aritmética se desborda? ¿Por qué los enteros negativos entre 2 63 y 2 64 -1 están expuestos al usuario final cuando está evaluando algo? - ¿He leído en alguna parte que un poco de sabor de UNIX puede cambiar interactivamente ULONG_MAX? ¿Alguien ha oído hablar de esto?
- Si alguien cambia arbitrariamente el valor del máximo entero sin signo
limits.h
, luego vuelve a compilarbash
, ¿qué podemos esperar que suceda?
Nota
1. Quería ilustrar más claramente lo que vi, ya que es algo empírico muy simple. Lo que noté es que:
- (a) Cualquier evaluación que dé <2 ^ 63-1 es correcta
- (b) Cualquier evaluación que dé => 2 ^ 63 hasta 2 ^ 64 da un número entero negativo:
- El rango de ese entero es x a y. x = -9223372036854775808 e y = 0.
Considerando esto, una evaluación que es como (b) puede expresarse como 2 ^ 63-1 más algo dentro de x..y. Por ejemplo, si literalmente se nos pide evaluar (2 ^ 63-1) +100 002 (pero podría ser cualquier número menor que en (a)) obtenemos -9223372036854675807. Solo estoy afirmando lo obvio, supongo, pero esto también significa que las dos siguientes expresiones:
- (2 ^ 63-1) + 100 002 Y;
- (2 ^ 63-1) + (LLONG_MAX - {lo que nos da el shell ((2 ^ 63-1) + 100 002), que es -9223372036854675807}), usando valores positivos que tenemos;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100 000
están muy cerca de hecho. La segunda expresión es "2" aparte de (2 ^ 63-1) + 100 002, es decir, lo que estamos evaluando. Esto es lo que quiero decir con que obtienes enteros negativos que te muestran qué tan lejos estás de 2 ^ 64. Quiero decir, con esos enteros negativos y conocimiento de los límites, bueno, no puedes terminar la evaluación dentro del rango x..y en el shell bash, pero puedes hacerlo en otro lugar: los datos se pueden usar hasta 2 ^ 64 en ese sentido (podría agregar póngalo en papel o úselo en bc). Más allá de eso, sin embargo, el comportamiento es similar al de 6 ^ 6 ^ 6 ya que el límite se alcanza como se describe a continuación en la Q ...
bc
, por ejemplo: $num=$(echo 6^6^6 | bc)
. Desafortunadamente, bc
pone saltos de línea, por lo que debe hacerlo num=$(echo $num | sed 's/\\\s//g')
después; Si lo hace en una tubería, hay caracteres de nueva línea reales, que son incómodos con sed, aunque num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
funciona. En cualquiera de los casos que ahora tiene un número entero que puede ser utilizado, por ejemplo, num2=$(echo "$num * 2" | bc)
.