Existem limites definidos para os recursos de avaliação aritmética do bash
shell. O manual é sucinto sobre esse aspecto da aritmética da casca, mas afirma :
A avaliação é feita em números inteiros de largura fixa sem verificação de estouro, embora a divisão por 0 seja interceptada e sinalizada como um erro. Os operadores e sua precedência, associatividade e valores são os mesmos da linguagem C.
Qual número inteiro de largura fixa a que isso se refere é realmente sobre qual tipo de dados é usado (e as especificidades de por que isso está além disso), mas o valor limite é expresso /usr/include/limits.h
dessa maneira:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
E depois que você souber disso, poderá confirmar esse estado de fato da seguinte maneira:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Este é um número inteiro de 64 bits e isso se traduz diretamente no shell no contexto da avaliação 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
Portanto, entre 2 63 e 2 64 -1, você obtém números inteiros negativos mostrando a que distância de ULONG_MAX você está 1 . Quando a avaliação atinge esse limite e transborda, por qualquer ordem que seja, você não recebe nenhum aviso e parte da avaliação é redefinida para 0, o que pode gerar um comportamento incomum com algo como exponenciação associativa correta, por exemplo:
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
...
O uso sh -c 'command'
não muda nada, portanto, devo assumir que essa é uma saída normal e compatível. Agora que acho que tenho um entendimento básico, mas concreto, do alcance e limite aritmético e do que isso significa no shell para avaliação de expressão, pensei em poder rapidamente examinar quais tipos de dados os outros softwares no Linux usam. Eu usei algumas bash
fontes que tive para complementar a entrada deste 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)
Há mais saída com as if
instruções e eu posso procurar por um comando como awk
também etc. Percebo que a expressão regular que usei não capta nada sobre ferramentas de precisão arbitrárias que tenho como bc
e dc
.
Questões
- Qual é a razão para não avisá-lo (como
awk
faz ao avaliar 2 ^ 1024) quando sua avaliação aritmética transborda? Por que os números inteiros negativos entre 2 63 e 2 64 -1 são expostos ao usuário final quando ele está avaliando algo? - Eu li em algum lugar que algum sabor do UNIX pode alterar interativamente o ULONG_MAX? Alguém já ouviu falar disso?
- Se alguém alterar arbitrariamente o valor do número inteiro não assinado máximo em
limits.h
e recompilarbash
, o que podemos esperar que aconteça?
Nota
1. Eu queria ilustrar mais claramente o que vi, pois é uma coisa empírica muito simples. O que eu notei é que:
- (a) Qualquer avaliação que dê <2 ^ 63-1 está correta
- (b) Qualquer avaliação que dê => 2 ^ 63 até 2 ^ 64 fornece um número inteiro negativo:
- O intervalo desse número inteiro é x a y. x = -9223372036854775808 e y = 0.
Considerando isso, uma avaliação semelhante a (b) pode ser expressa como 2 ^ 63-1 mais algo dentro de x..y. Por exemplo, se formos literalmente solicitados a avaliar (2 ^ 63-1) +100 002 (mas pode ser qualquer número menor que em (a)), obtemos -9223372036854675807. Estou apenas afirmando o óbvio, mas acho que isso também significa que as duas expressões a seguir:
- (2 ^ 63-1) + 100 002 AND;
- (2 ^ 63-1) + (LLONG_MAX - {o que o shell nos dá ((2 ^ 63-1) + 100 002), que é -9223372036854675807}) bem, usando valores positivos que temos;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100 000
são muito próximos mesmo. A segunda expressão é "2" além de (2 ^ 63-1) + 100 002, isto é, o que estamos avaliando. É isso que quero dizer com números inteiros negativos, mostrando a que distância de 2 ^ 64 você está. Quero dizer, com esses números inteiros negativos e conhecimento dos limites, bem, você não pode concluir a avaliação dentro do intervalo x..y no shell bash, mas pode em outro lugar - os dados são utilizáveis até 2 ^ 64 nesse sentido (eu poderia acrescentar no papel ou em bc). Além disso, porém, o comportamento é semelhante ao de 6 ^ 6 ^ 6, pois o limite é atingido como descrito abaixo no Q ...
bc
, como por exemplo: $num=$(echo 6^6^6 | bc)
. Infelizmente, bc
coloca quebras de linha, então você precisa num=$(echo $num | sed 's/\\\s//g')
depois; se você fizer isso em um pipe, existem caracteres de nova linha reais, que são estranhos com sed, embora num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
funcionem. Em ambos os casos, agora você tem um número inteiro que pode ser usado, por exemplo num2=$(echo "$num * 2" | bc)
,.
bc
configurando BC_LINE_LENGTH=0
.