Bash kabuğunun aritmetik taşma vb. Konusunda sizi uyarmamasının mantığı nedir?


9

bashKabuğun aritmetik değerlendirme yetenekleri için belirlenmiş sınırlar vardır . Kılavuzu kabuk aritmetik ancak bu yönüyle ilgili özlü durumları :

Değerlendirme, taşma kontrolü olmaksızın sabit genişlikli tamsayılarda yapılır, ancak 0'a bölünme bir hapsolur ve bir hata olarak işaretlenir. İşleçler ve öncelikleri, ilişkilendirilebilirlikleri ve değerleri C dilindekilerle aynıdır.

Bunun hangi sabit genişlikli tamsayıya başvurduğu gerçekten hangi veri türünün kullanıldığı (ve bunun neden bunun ötesinde olduğunun özellikleri) ile ilgilidir, ancak sınır değeri /usr/include/limits.hbu şekilde ifade edilir :

#  if __WORDSIZE == 64
#   define ULONG_MAX     18446744073709551615UL
#  ifdef __USE_ISOC99
#  define LLONG_MAX       9223372036854775807LL
#  define ULLONG_MAX    18446744073709551615ULL

Ve bunu öğrendikten sonra, bu durumun böyle olduğunu teyit edebilirsiniz:

# getconf -a | grep 'long'
LONG_BIT                           64
ULONG_MAX                          18446744073709551615

Bu 64 bitlik bir tamsayıdır ve aritmetik değerlendirme bağlamında doğrudan kabukta tercüme edilir:

# 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

Yani 2 ila 63 ve 2 64 -1, sen ne kadar ileri olduğunu ULONG_MAX yola gösteren negatif tamsayılar olsun 1 . Ne zaman böyle bir şey ile bazı sıradışı davranışı elde edebilir 0'a sıfırlanır, hiçbir uyarı ve değerlendirme kısmını get olduğunu gelseler tarafından limit ve taşmaları, o değerlendirme ulaştığı sağ ilişkisel örneği için üs:

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
...

Kullanmak sh -c 'command'hiçbir şeyi değiştirmez, bu yüzden bunun normal ve uyumlu çıktı olduğunu varsaymak zorundayım. Artık aritmetik aralık ve sınır ve ifade değerlendirmesi için kabuğunda ne anlama geldiğine dair temel ama somut bir anlayışa sahip olduğumu düşündüğüme göre, Linux'taki diğer yazılımların hangi veri türlerini kullandığına hızlıca bakabileceğimi düşündüm. bashBu komutun girdisini tamamlamak için bazı kaynaklar kullandım :

{ 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İfadeler ile daha fazla çıktı var ve ben awkde vb gibi bir komut arayabilirsiniz . Kullandığım düzenli ifadenin bcve gibi keyfi hassas araçlar hakkında bir şey yakalamadığını fark ettim dc.


Sorular

  1. awkAritmetik değerlendirmeniz aşıldığında sizi uyarmamanın mantığı nedir ( 2 ^ 1024 değerini değerlendirirken olduğu gibi )? Bir şeyi değerlendirirken neden son 63'e 2 63 ve 2 64 -1 arasındaki negatif tamsayılar maruz kalıyor?
  2. Bazı UNIX lezzet etkileşimli ULONG_MAX değişebilir bir yerde okudum? Bunu duyan var mı?
  3. Birisi işaretsiz tam sayı maksimum değerini keyfi olarak değiştirirse, limits.hyeniden derlerse bash, ne olmasını bekleyebiliriz?

Not

1. Çok basit ampirik şeyler olduğu için gördüklerimi daha net bir şekilde göstermek istedim. Ne fark ettim:

  • (a) <2 ^ 63-1 veren herhangi bir değerlendirme doğrudur
  • (b) => 2 ^ 63'e kadar 2 ^ 64 veren herhangi bir değerlendirme negatif bir tamsayı verir:
    • Bu tamsayının aralığı x ila y'dir. x = -9223372036854775808 ve y = 0.

Bu göz önüne alındığında, (b) gibi bir değerlendirme 2 ^ 63-1 artı x..y içinde bir şey olarak ifade edilebilir. Örneğin, kelimenin tam anlamıyla (2 ^ 63-1) +100 002'yi değerlendirmemiz istendiğinde (ancak (a) 'dan daha küçük bir sayı olabilir) -9223372036854675807 alırız. Ben sadece sanırım bariz söylüyorum ama bu aynı zamanda aşağıdaki iki ifade anlamına gelir:

  • (2 ^ 63-1) + 100002 VE;
  • (2 ^ 63-1) + (LLONG_MAX - {kabuğun bize ne verdiğini ((2 ^ 63-1) + 100 002), ki bu -9223372036854675807}) sahip olduğumuz pozitif değerleri kullanarak;
    • (2 ^ 63-1) + (9223372036854775807-9223372036854675807 = 100000)
    • = 9223372036854775807 + 100000

gerçekten çok yakın. İkinci ifade (2 ^ 63-1) + 100 002 dışında yani "2" dir. Demek istediğim, 2 ^ 64'ten ne kadar uzakta olduğunuzu gösteren negatif tamsayılar olsun. Demek istediğim, bu negatif tamsayılar ve sınırların bilgisi ile, bash kabuğundaki x..y aralığında değerlendirmeyi bitiremezsiniz, ancak başka bir yerde yapabilirsiniz - veriler bu anlamda 2 ^ 64'e kadar kullanılabilir (ekleyebilirim kağıt üzerine koyun veya bc'de kullanın). Ancak bunun ötesinde, aşağıdaki Q'da açıklandığı gibi sınıra ulaşıldığından davranış 6 ^ 6 ^ 6 ile benzerdir ...


5
Benim tahminim, mantık "kabuk matematik için doğru araç değil" kadar kaynar. Bunun için tasarlanmamıştır ve gösterdiğiniz gibi zarif bir şekilde başa çıkmaya çalışmaz. Cehennem, çoğu mermi şamandıralarla bile ilgilenmiyor!
terdon

@terdon Her ne kadar kabuğun bu durumda sayılarla uğraşma şekli, şimdiye kadar duyduğum her üst düzey dil ile tamamen aynıdır. Tamsayı türleri sabit bir boyuttadır ve taşabilir.
goldilocks

@terdon Gerçekten, ben 6 ^ 6 ^ 6 zamanlama QI bunu fark beri bu araştırma gibi. Ayrıca fazla içerik bulamamanın nedeninin bunun C, hatta C99 ile ilgisi olmasından kaynaklandığını da tahmin ettim. Ne bir geliştirici ne de bir BT insanı olduğum için, bu varsayımları temel alan tüm bilgileri kabul etmem gerekiyor. Kesinlikle keyfi hassasiyet gerektiren biri veri türünü biliyor ama açıkçası ben o kişi değilim :) (ama awk davranışını @ 2 ^ 53 + 1 yani çift yüzer; sadece hassas ve iç baskı vb. !).

1
Kabukta büyük sayılarla çalışmak istiyorsanız bc, örneğin: kullanın $num=$(echo 6^6^6 | bc). Ne yazık ki, bcsatır sonları koyar, bu yüzden num=$(echo $num | sed 's/\\\s//g')daha sonra yapmanız gerekir ; eğer bir boruda yaparsanız, sed ile garip olan gerçek yeni satır karakterleri vardır, ancak num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')çalışır. Her iki durumda da, artık kullanılabilecek bir tamsayı vardır, örneğin num2=$(echo "$num * 2" | bc).
goldilocks

1
... Burada birisi bc, ayarlayarak bu satır sonu özelliğini devre dışı bırakabileceğinizi belirtti BC_LINE_LENGTH=0.
goldilocks

Yanıtlar:


11

Yani 2 ^ 63 ve 2 ^ 64-1 arasında, ULONG_MAX'tan ne kadar uzakta olduğunuzu gösteren negatif tamsayılar elde edersiniz.

Hayır. Bunu nasıl anlıyorsunuz? Kendi örneğinizle, maks:

> max=$((2**63 - 1)); echo $max
9223372036854775807

"Taşma" demek, ULONG_MAX'tan ne kadar uzakta olduğunuzu gösteren negatif tamsayılar elde ediyorsanız "ise, buna bir tane eklersek -1 almamalıyız? Ama velakin:

> echo $(($max + 1))
-9223372036854775808

Belki de $maxbunun negatif bir fark elde etmek için ekleyebileceğiniz bir sayı olduğunu kastediyorsunuz , çünkü:

> echo $(($max + 1 + $max))
-1

Ancak bu gerçek olmaya devam etmez:

> echo $(($max + 2 + $max))
0

Bunun nedeni, sistemin imzalı tam sayıları uygulamak için ikisinin tamamlayıcısını kullanmasıdır. 1 Taşma sonucu ortaya çıkan değer , size bir fark, negatif bir fark, vb . Sağlama girişimi DEĞİLDİR. Kelimenin tam anlamıyla bir değeri sınırlı sayıda bite kısaltmanın, ardından ikinin tamamlayıcı işaretli tamsayısı olarak yorumlanmasının sonucudur. . Örneğin, bunun nedeni $(($max + 1 + $max))-1 olarak ortaya çıkmasıdır, çünkü ikinin tamamlayıcısındaki en yüksek değer , en yüksek bit (negatif gösteren) dışında ayarlanan tüm bitlerdir ; Bunları bir araya getirmek, temel olarak tüm bitleri sola taşımak anlamına gelir, böylece sonuç elde edersiniz (boyut 64 bit değilse, 16 bitse):

11111111 11111110

Yüksek (işaret) biti artık ekte taşındığı için ayarlanmıştır. Buna bir tane daha eklerseniz (00000000 00000001) , ikisinin tamamlayıcısı -1 olan tüm bitleri ayarlamış olursunuz .

Sanırım ilk sorunuzun ikinci yarısına kısmen cevap veriyor - "Negatif tamsayılar ... son kullanıcıya maruz kalıyor?" Birincisi, bu 64-bit ikisinin tamamlayıcı sayılarının kurallarına göre doğru değerdir. Bu, çoğu (diğer) genel amaçlı yüksek düzeyli programlama dillerinin geleneksel uygulamasıdır (bunu yapmayan bir dil düşünemiyorum), bu yüzden bashkonvansiyona bağlı kalmaktır. İlk sorunun ilk kısmı olan “Mantık Nedir?” Cevabı da hangisidir: bu programlama dillerinin belirtimindeki normdur.

WRT 2. soru, etkileşimli ULONG_MAX değişen sistemleri duymadım.

Birisi sınırsız olarak imzasız tam sayı maksimum değerini değiştirirse. H, sonra bash'ı yeniden derlerse, ne olmasını bekleyebiliriz?

Aritmetiğin nasıl ortaya çıktığı konusunda herhangi bir fark yaratmaz, çünkü bu, sistemi yapılandırmak için kullanılan rastgele bir değer değildir - donanımı yansıtan değişmez bir sabiti depolayan bir kolaylık değeridir. Benzer şekilde, c'yi 55 mil / saat olarak yeniden tanımlayabilirsiniz , ancak ışığın hızı hala saniyede 186.000 mil olacaktır. c , evreni yapılandırmak için kullanılan bir sayı değildir - evrenin doğası hakkında bir çıkarımdır.

ULONG_MAX tamamen aynı. N-bit sayılarının niteliğine göre çıkarılır / hesaplanır. Eğer bu sabit, sistemin gerçekliğini temsil ettiği varsayılırsa bir yerde kullanılırsa, onu değiştirmek limits.hçok kötü bir fikir olacaktır .

Ve donanımınızın empoze ettiği gerçeği değiştiremezsiniz.


1. Bunun (tamsayı temsil aracı) bashtemelde C kütüphanesine bağlı olduğu ve C standardının bunu garanti etmediği için garanti edildiğini sanmıyorum . Ancak, çoğu modern modern bilgisayarda kullanılan budur.


Çok müteşekkirim! Odadaki fil ile uzlaşmak ve düşünmek. Evet ilk bölümde çoğunlukla kelimelerle ilgili. Ne demek istediğimi göstermek için Q'umu güncelledim. Neden ikisinin tamamlayıcısının gördüklerimden bazılarını açıkladığını ve cevabınızın bunu anlamada paha biçilmez olduğunu araştıracağım! UNIX Q ile ilgili olarak, burada AIX ile ARG_MAX hakkında bir şeyler yanlış okumalıyım . Şerefe!

1
Aslında açıkladığınız gibi > 2 * aralığında olduğunuzdan eminseniz değeri belirlemek için ikisinin tamamlayıcısını kullanabilirsiniz $max. Puanlarım 1) amaç değil, 2) bunu yapmak isteyip istemediğinizi anladığınızdan emin olun, 3) çok sınırlı uygulanabilirliği nedeniyle çok kullanışlı değil, 4) dipnota göre sistemin aslında ikisinin tamamlayıcısı kullanın. Kısacası, program kodunda bu durumdan yararlanmaya çalışmak çok kötü bir uygulama olarak görülecektir. "Çok sayıda" kütüphane / modül vardır (POSIX altındaki mermiler için bc) - gerekiyorsa kullanın.
goldilocks

Sadece son zamanlarda hızlı taşıma IC ile 4-bit ikili toplayıcı ile bir ALU uygulamak için iki tamamlayıcı kaldı bir şey izledi; bir kişinin tamamlayıcısı ile bir karşılaştırma bile vardı (nasıl olduğunu görmek için). Açıklamanız burada gördüğüm şeyi bu videolarda tartışılanlarla adlandırabilmem ve bağlayabilmemde etkili oldu , hepsi battığında tüm sonuçları gerçekten kavrayabilme şansımı artırdım. Bunun için tekrar teşekkürler! Şerefe!
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.