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ê precisanum=$(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, emboranum=$(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 exemplonum2=$(echo "$num * 2" | bc)
,.bc
configurandoBC_LINE_LENGTH=0
.Respostas:
Não. Como você acha isso? Por seu próprio exemplo, o máximo é:
Se "overflow" significou "você obtém números inteiros negativos mostrando a que distância de ULONG_MAX você está", se adicionarmos um a isso, não deveríamos obter -1? Mas ao invés:
Talvez você queira dizer que este é um número que você pode adicionar
$max
para obter uma diferença negativa, pois:Mas isso de fato não se mantém verdadeiro:
Isso ocorre porque o sistema usa o complemento de dois para implementar números inteiros assinados. 1 O valor resultante de um estouro NÃO é uma tentativa de fornecer uma diferença, uma diferença negativa, etc. É literalmente o resultado de truncar um valor para um número limitado de bits e depois interpretá-lo como um inteiro assinado de complemento de dois . Por exemplo, o motivo
$(($max + 1 + $max))
aparece como -1, porque o valor mais alto no complemento de dois é todos os bits configurados, exceto o bit mais alto (que indica negativo); juntá-los basicamente significa carregar todos os bits para a esquerda, para que você acabe (se o tamanho fosse 16 bits e não 64):O bit alto (sinal) agora está definido porque foi transferido na adição. Se você adicionar mais um (00000000 00000001) a isso, todos os bits serão configurados , o que no complemento de dois é -1.
Eu acho que isso responde parcialmente à segunda metade da sua primeira pergunta - "Por que os números inteiros negativos são expostos ao usuário final?". Primeiro, porque esse é o valor correto de acordo com as regras dos números complementares de dois bits de 64 bits. Essa é a prática convencional da maioria das (outras) linguagens de programação de alto nível de uso geral (não consigo pensar em uma que não faça isso), por isso
bash
é aderente à convenção. Qual é também a resposta para a primeira parte da primeira pergunta - "Qual é a lógica?": Essa é a norma na especificação de linguagens de programação.WRT a segunda pergunta, eu não ouvi falar de sistemas que mudam interativamente ULONG_MAX.
Não faria nenhuma diferença na forma como a aritmética se sai, porque esse não é um valor arbitrário usado para configurar o sistema - é um valor de conveniência que armazena uma constante imutável refletindo o hardware. Por analogia, você pode redefinir c para 55 mph, mas a velocidade da luz ainda será de 186.000 milhas por segundo. c não é um número usado para configurar o universo - é uma dedução sobre a natureza do universo.
ULONG_MAX é exatamente o mesmo. É deduzido / calculado com base na natureza dos números de N bits. Mudá-lo
limits.h
seria uma péssima idéia se essa constante for usada em algum lugar, supondo que ela represente a realidade do sistema .E você não pode mudar a realidade imposta pelo seu hardware.
1. Eu não acho que isso (o meio de representação de número inteiro) seja realmente garantido por
bash
, uma vez que depende da biblioteca C subjacente e o padrão C não garante isso. No entanto, é isso que é usado na maioria dos computadores modernos normais.fonte
$max
, conforme descreve. Meus pontos são: 1) esse não é o objetivo; 2) certifique-se de entender se você quer fazer isso; 3) não é muito útil por causa da aplicabilidade muito limitada; 4) conforme a nota de rodapé, não é realmente garantido que o sistema funcione. use o complemento de dois. Em resumo, tentar explorar isso no código do programa seria considerado uma prática muito ruim. Existem bibliotecas / módulos de "grande número" (para shells no POSIXbc
) - use-os se for necessário.