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 hacerlonum=$(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, aunquenum=$(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)
.bc
configurandoBC_LINE_LENGTH=0
.Respuestas:
No. ¿Cómo te imaginas eso? Por su propio ejemplo, el máximo es:
Si "desbordamiento" significaba "obtienes enteros negativos que te muestran qué tan lejos estás de ULONG_MAX", entonces si agregamos uno a eso, ¿no deberíamos obtener -1? Pero en vez:
Quizás quiera decir que este es un número al que puede agregar
$max
para obtener una diferencia negativa, ya que:Pero esto de hecho no sigue siendo cierto:
Esto se debe a que el sistema utiliza el complemento de dos para implementar enteros con signo. 1 El valor resultante de un desbordamiento NO es un intento de proporcionarle una diferencia, una diferencia negativa, etc. Es literalmente el resultado de truncar un valor a un número limitado de bits, y luego interpretarlo como un entero con signo de complemento a dos . Por ejemplo, la razón
$(($max + 1 + $max))
es que -1 es porque el valor más alto en el complemento de dos es todos los bits establecidos, excepto el bit más alto (que indica negativo); Sumar estos juntos básicamente significa llevar todos los bits a la izquierda para que termines con (si el tamaño fuera de 16 bits y no 64):El bit alto (signo) ahora se establece porque se transfirió en la adición. Si agrega uno más (00000000 00000001) a eso, entonces tiene todos los bits establecidos , que en el complemento de dos es -1.
Creo que eso responde parcialmente a la segunda mitad de su primera pregunta: "¿Por qué los enteros negativos ... están expuestos al usuario final?". Primero, porque ese es el valor correcto de acuerdo con las reglas de los números de complemento a dos de 64 bits. Esta es la práctica convencional de la mayoría (otros) lenguajes de programación de alto nivel de propósito general (no puedo pensar en uno que no haga esto), por lo que
bash
se adhiere a la convención. ¿Cuál es también la respuesta a la primera parte de la primera pregunta: "¿Cuál es la razón?": Esta es la norma en la especificación de los lenguajes de programación.WRT la segunda pregunta, no he oído hablar de sistemas que cambian interactivamente ULONG_MAX.
No haría ninguna diferencia en cómo sale la aritmética, porque este no es un valor arbitrario que se utiliza para configurar el sistema, es un valor de conveniencia que almacena una constante inmutable que refleja el hardware. Por analogía, podría redefinir c a 55 mph, pero la velocidad de la luz seguirá siendo de 186,000 millas por segundo. c no es un número utilizado para configurar el universo, es una deducción sobre la naturaleza del universo.
ULONG_MAX es exactamente lo mismo. Se deduce / calcula en función de la naturaleza de los números de N bits. Cambiarlo
limits.h
sería una muy mala idea si esa constante se usa en algún lugar suponiendo que se supone que representa la realidad del sistema .Y no puede cambiar la realidad impuesta por su hardware.
1. No creo que esto (el medio de representación de enteros) esté realmente garantizado
bash
, ya que depende de la biblioteca C subyacente y el estándar C no garantiza eso. Sin embargo, esto es lo que se usa en la mayoría de las computadoras modernas normales.fuente
$max
, como lo describe. Mis puntos son 1) ese no es el propósito, 2) asegúrese de entender si quiere hacer eso, 3) no es muy útil debido a la aplicabilidad muy limitada, 4) según la nota al pie de página, en realidad no está garantizado que el sistema lo haga usa el complemento de dos. En resumen, tratar de explotar eso en el código del programa se consideraría una práctica muy pobre. Hay bibliotecas / módulos de "gran número" (para shells en POSIXbc
). Úselos si es necesario.