Pro aritmetické vyhodnocovací schopnosti bash
jsou nastaveny limity skořápka. Manuál je o tomto aspektu aritmetiky shellu stručný, ale uvádí:
Vyhodnocení se provádí v celých číslech s pevnou šířkou bez kontroly přetečení,
ačkoli dělení 0 je zachyceno a označeno jako chyba. Operátory
a jejich priorita, asociativita a hodnoty jsou stejné jako v
jazyce C.
Na jaké celé číslo s pevnou šířkou se to vztahuje, záleží ve skutečnosti na jakém typu dat se používá (a specifika, proč tomu tak je, jsou mimo toto), ale limitní hodnota je vyjádřena v /usr/include/limits.h
tímto způsobem:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
A jakmile to budete vědět, můžete tento fakt potvrdit takto:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Toto je 64bitové celé číslo a to se překládá přímo do shellu v kontextu aritmetického vyhodnocení:
# 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
Mezi 2 a 2-1 tedy získáte záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX. Když vyhodnocení dosáhne tohoto limitu a přeteče, v jakémkoli pořadí, nedostanete žádné varování a ta část vyhodnocení se resetuje na 0, což může způsobit neobvyklé chování s něčím jako pravá asociativní například umocňování:
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
...
Pomocí sh -c 'command'
nic nemění, takže musím předpokládat, že je to normální a vyhovující výstup. Nyní, když si myslím, že mám základní, ale konkrétní pochopení aritmetického rozsahu a limitu a toho, co to znamená v shellu pro vyhodnocení výrazů, myslel jsem, že bych mohl rychle nakouknout, jaké datové typy používá jiný software v Linuxu. Použil jsem nějaký bash
Zdroje Musel jsem doplnit vstup tohoto příkazu:
{ 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)
if
nabízí více výstupů příkazy a mohu hledat příkaz jako awk
taky atd. Všiml jsem si, že regulární výraz, který jsem použil, nezachycuje nic o libovolných přesných nástrojích, které mám, jako je bc
a dc
.
Otázky
- Jaký je důvod, proč vás nevarovat (jako
awk
dělá při vyhodnocení 2^1024), když vaše aritmetické vyhodnocení přeteče? Proč jsou koncovému uživateli vystavena záporná celá čísla mezi 2 a 2-1, když něco hodnotí? - Někde jsem četl, že některá varianta UNIXu může interaktivně změnit ULONG_MAX? Slyšel jste o tom někdo?
- Pokud někdo svévolně změní hodnotu maximálního čísla bez znaménka v
limits.h
, poté znovu zkompilujebash
, co můžeme očekávat, že se stane?
Přijatá odpověď:
Mezi 2^63 a 2^64-1 tedy získáte záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX.
Ne. Jak jste na to přišel? Ve vašem vlastním příkladu je maximum:
> max=$((2**63 - 1)); echo $max
9223372036854775807
Pokud „přetečení“ znamenalo „dostanete záporná celá čísla, která vám ukazují, jak daleko jste od ULONG_MAX“, pak když k tomu přidáme jedno, neměli bychom dostat -1? Ale místo toho:
> echo $(($max + 1))
-9223372036854775808
Možná chcete říct, že toto je číslo, které můžete přidat do $max
získat záporný rozdíl, protože:
> echo $(($max + 1 + $max))
-1
To však ve skutečnosti nadále neplatí:
> echo $(($max + 2 + $max))
0
Je to proto, že systém používá k implementaci celých čísel se znaménkem dvojkový doplněk. Hodnota vyplývající z přetečení NENÍ pokusem poskytnout vám rozdíl, záporný rozdíl atd. Je to doslova výsledek zkrácení hodnoty na omezený počet bitů a poté její interpretace jako celé číslo se znaménkem dvojky. Například důvod $(($max + 1 + $max))
vyjde jako -1, protože nejvyšší hodnota v doplňku dvou je nastavena všemi bity kromě nejvyšší bit (který označuje záporný); sčítat je dohromady v podstatě znamená přenést všechny bity doleva, takže skončíte s (pokud by velikost byla 16 bitů, a ne 64):
11111111 11111110
Vysoký (znaménkový) bit je nyní nastaven, protože byl přenesen do sčítání. Pokud k tomu přidáte ještě jeden (00000000 00000001), máte všechny bity nastaveny , což ve dvojkovém doplňku je -1.
Myslím, že to částečně odpovídá na druhou polovinu vaší první otázky — „Proč jsou záporná celá čísla… vystavena koncovému uživateli?“. Za prvé, protože to je správná hodnota podle pravidel 64bitových čísel dvojkového doplňku. Toto je běžná praxe většiny (ostatních) obecných programovacích jazyků na vysoké úrovni (nenapadá mě žádný, který by to neuměl), takže bash
dodržuje konvenci. Což je také odpověď na první část první otázky — „Jaké je zdůvodnění?“:toto je norma ve specifikaci programovacích jazyků.
WRT 2. otázka, neslyšel jsem o systémech, které interaktivně mění ULONG_MAX.
Pokud někdo svévolně změní hodnotu maxima bez znaménka v limitech.h, pak znovu zkompiluje bash, co můžeme očekávat, že se stane?
Nezáleželo by na tom, jak aritmetika vyjde, protože to není libovolná hodnota, která se používá ke konfiguraci systému – je to hodnota pro pohodlí, která ukládá neměnnou konstantu odrážející hardware. Analogicky můžete předefinovat c být 55 mph, ale rychlost světla bude stále 186 000 mil za sekundu. c není číslo používané ke konfiguraci vesmíru – je to dedukce o povaze vesmíru.
Související:Python – Žádný takový soubor nebo adresář, ale vidím to!?
ULONG_MAX je úplně stejný. Je odvozen/vypočítán na základě povahy N-bitových čísel. Změna v limits.h
byl by velmi špatný nápad, kdyby se tato konstanta někde použila za předpokladu, že má reprezentovat realitu systému .
A realitu vnucenou vaším hardwarem nemůžete změnit.