Apa alasan untuk bash shell tidak memperingatkan Anda tentang aritmatika melimpah dll?

9

Ada batas yang ditetapkan untuk kemampuan evaluasi aritmatika dari bashshell. Manual ini ringkas tentang aspek aritmatika shell ini tetapi menyatakan :

Evaluasi dilakukan dalam bilangan bulat dengan lebar tetap tanpa pemeriksaan overflow, meskipun pembagian dengan 0 terperangkap dan ditandai sebagai kesalahan. Operator dan prioritas, asosiatif, dan nilai-nilai mereka sama seperti dalam bahasa C.

Bilangan bulat dengan lebar tetap yang merujuk ini benar-benar tentang tipe data mana yang digunakan (dan spesifik mengapa hal ini berada di luar ini) tetapi nilai batas dinyatakan dengan /usr/include/limits.hcara ini:

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

Dan begitu Anda tahu itu, Anda dapat mengkonfirmasi keadaan fakta ini seperti:

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

Ini adalah integer 64 bit dan ini diterjemahkan secara langsung dalam shell dalam konteks evaluasi aritmatika:

# 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

Jadi antara 2 63 dan 2 64 -1, Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX Anda 1 . Ketika evaluasi mencapai batas itu dan meluap, dengan urutan apa pun itu, Anda tidak mendapat peringatan dan bagian dari evaluasi diatur ulang ke 0 yang dapat menghasilkan beberapa perilaku tidak biasa dengan sesuatu seperti eksponensial asosiatif-kanan misalnya:

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

Menggunakan sh -c 'command'tidak mengubah apa pun jadi saya harus menganggap ini adalah output yang normal dan sesuai. Sekarang saya pikir saya memiliki pemahaman dasar tapi konkret tentang rentang dan batas aritmatika dan apa artinya di shell untuk evaluasi ekspresi, saya pikir saya bisa dengan cepat mengintip pada tipe data apa yang digunakan perangkat lunak lain dalam Linux. Saya menggunakan beberapa bashsumber yang saya miliki untuk melengkapi input dari perintah ini:

{ 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)

Ada lebih banyak output dengan ifpernyataan dan saya dapat mencari perintah seperti awkjuga dll. Saya melihat ekspresi reguler yang saya gunakan tidak menangkap apa pun tentang alat presisi sewenang-wenang yang saya miliki seperti bcdan dc.


Pertanyaan

  1. Apa alasan untuk tidak memperingatkan Anda (seperti awkhalnya ketika mengevaluasi 2 ^ 1024) ketika evaluasi aritmatika Anda meluap? Mengapa bilangan bulat negatif antara 2 63 dan 2 64 -1 terkena pengguna akhir ketika dia mengevaluasi sesuatu?
  2. Saya telah membaca bahwa rasa UNIX dapat mengubah ULONG_MAX secara interaktif? Adakah yang pernah mendengar ini?
  3. Jika seseorang secara sewenang-wenang mengubah nilai maksimum integer unsigned limits.h, lalu mengkompilasi ulang bash, apa yang dapat kita harapkan akan terjadi?

Catatan

1. Saya ingin mengilustrasikan lebih jelas apa yang saya lihat, karena itu adalah hal-hal empiris yang sangat sederhana. Yang saya perhatikan adalah:

  • (a) Setiap evaluasi yang memberikan <2 ^ 63-1 adalah benar
  • (B) Setiap evaluasi yang memberi => 2 ^ 63 hingga 2 ^ 64 memberikan bilangan bulat negatif:
    • Kisaran bilangan bulat itu adalah x hingga y. x = -9223372036854775808 dan y = 0.

Mempertimbangkan hal ini, evaluasi yang seperti (b) dapat dinyatakan sebagai 2 ^ 63-1 ditambah sesuatu dalam x..y. Sebagai contoh jika kita benar-benar diminta untuk mengevaluasi (2 ^ 63-1) +100 002 (tetapi bisa lebih kecil daripada di (a)) kita mendapatkan -9223372036854675807. Saya hanya menyatakan yang jelas saya kira tetapi ini juga berarti bahwa dua ekspresi berikut:

  • (2 ^ 63-1) + 100 002 DAN;
  • (2 ^ 63-1) + (LLONG_MAX - {apa yang diberikan shell kepada kami ((2 ^ 63-1) + 100 002), yaitu -9223372036854675807}) dengan baik, menggunakan nilai positif yang kami miliki;
    • (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100.000

memang sangat dekat. Ekspresi kedua adalah "2" terpisah dari (2 ^ 63-1) + 100 002 yaitu apa yang kami evaluasi. Inilah yang saya maksudkan dengan Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari 2 ^ 64. Maksud saya dengan bilangan bulat negatif dan pengetahuan tentang batas, yah Anda tidak bisa menyelesaikan evaluasi dalam kisaran x..y di bash shell tetapi Anda bisa di tempat lain - data dapat digunakan hingga 2 ^ 64 dalam hal itu (saya bisa menambahkan itu di atas kertas atau menggunakannya dalam bc). Di luar itu, bagaimanapun, perilaku ini mirip dengan 6 ^ 6 ^ 6 karena batas tercapai seperti yang dijelaskan di bawah ini dalam Q ...


sumber
5
Dugaan saya adalah bahwa alasannya bermuara pada "shell bukan alat yang tepat untuk matematika". Itu tidak dirancang untuk itu dan tidak berusaha menghadapinya dengan anggun seperti yang Anda tunjukkan. Sial, kebanyakan kerang bahkan tidak berurusan dengan pelampung!
terdon
@terdon Meskipun cara shell menangani angka-angka dalam kasus ini persis sama dengan setiap bahasa tingkat tinggi yang pernah saya dengar. Jenis integer adalah ukuran tetap dan bisa meluap.
goldilocks
@terdon Memang, ketika saya meneliti ini sejak 6 ^ 6 ^ 6 waktu QI menyadari itu. Saya juga menebak alasan mengapa saya tidak dapat menemukan banyak konten adalah karena ini ada hubungannya dengan C, atau bahkan C99. Karena saya bukan pengembang atau orang IT, saya harus menerima semua pengetahuan yang melatarbelakangi asumsi ini. Tentunya seseorang yang membutuhkan presisi yang sewenang-wenang tahu tentang tipe data tetapi jelas saya bukan orang itu :) (tapi saya memang memperhatikan perilaku awk @ 2 ^ 53 + 1 yaitu float double; hanya presisi dan internal vs pencetakan dll. Di luar saya !).
1
Jika Anda ingin bekerja dengan angka besar di shell, penggunaan bc, misalnya: $num=$(echo 6^6^6 | bc). Sayangnya, bcmenempatkan jeda baris, jadi Anda harus num=$(echo $num | sed 's/\\\s//g')sesudahnya; jika Anda melakukannya di dalam pipa, ada karakter baris baru yang sebenarnya, yang canggung dengan sed, meskipun num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')berfungsi. Dalam kedua kasus Anda sekarang memiliki bilangan bulat yang dapat digunakan, misalnya num2=$(echo "$num * 2" | bc),.
goldilocks
1
... Seseorang di sini menunjukkan Anda dapat menonaktifkan fitur pemisah baris ini bcdengan mengatur BC_LINE_LENGTH=0.
goldilocks

Jawaban:

11

Jadi antara 2 ^ 63 dan 2 ^ 64-1, Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX.

Tidak. Bagaimana menurut Anda? Dengan contoh Anda sendiri, maks adalah:

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

Jika "overflow" berarti "Anda mendapatkan bilangan bulat negatif yang menunjukkan seberapa jauh Anda dari ULONG_MAX", maka jika kita menambahkannya, bukankah seharusnya kita mendapatkan -1? Melainkan:

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

Mungkin maksud Anda ini adalah angka yang dapat Anda tambahkan $maxuntuk mendapatkan perbedaan negatif, karena:

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

Tapi ini sebenarnya tidak terus berlaku:

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

Ini karena sistem menggunakan komplemen dua untuk mengimplementasikan bilangan bulat yang ditandatangani. 1 Nilai yang dihasilkan dari overflow BUKAN upaya untuk memberi Anda perbedaan, perbedaan negatif, dll. Ini secara harfiah merupakan hasil dari pemotongan nilai ke sejumlah bit, kemudian diartikan sebagai bilangan bulat bertanda tangan dua komplemen yang ditandatangani . Sebagai contoh, alasan $(($max + 1 + $max))keluar sebagai -1 adalah karena nilai tertinggi dalam komplemen dua adalah semua bit ditetapkan kecuali bit tertinggi (yang menunjukkan negatif); menambahkan ini bersama-sama pada dasarnya berarti membawa semua bit ke kiri sehingga Anda berakhir dengan (jika ukurannya 16-bit, dan bukan 64):

11111111 11111110

Bit (tanda) tinggi sekarang disetel karena terbawa dalam penambahan. Jika Anda menambahkan satu lagi (00000000 00000001) untuk itu, Anda kemudian memiliki semua bit yang ditetapkan , yang dalam komplemen dua adalah -1.

Saya pikir sebagian menjawab bagian kedua dari pertanyaan pertama Anda - "Mengapa bilangan bulat negatif ... terpapar pada pengguna akhir?". Pertama, karena itu adalah nilai yang benar sesuai dengan aturan nomor komplemen 64-bit two. Ini adalah praktik konvensional dari sebagian besar (lain) bahasa pemrograman tujuan umum tingkat tinggi (saya tidak bisa memikirkan satu yang tidak melakukan ini), jadi bashmematuhi konvensi. Yang juga merupakan jawaban untuk bagian pertama dari pertanyaan pertama - "Apa alasannya?": Ini adalah norma dalam spesifikasi bahasa pemrograman.

WRT pertanyaan ke-2, saya belum pernah mendengar tentang sistem yang mengubah ULONG_MAX secara interaktif.

Jika seseorang secara sewenang-wenang mengubah nilai maksimum bilangan bulat tak bertanda di dalam batas. H, lalu mengkompilasi ulang bash, apa yang bisa kita harapkan akan terjadi?

Tidak akan ada bedanya dengan bagaimana aritmatika keluar, karena ini bukan nilai arbitrer yang digunakan untuk mengkonfigurasi sistem - ini adalah nilai kenyamanan yang menyimpan konstanta yang tidak dapat diubah yang mencerminkan perangkat keras. Dengan analogi, Anda bisa mendefinisikan ulang c menjadi 55 mph, tetapi kecepatan cahaya akan tetap 186.000 mil per detik. c bukan angka yang digunakan untuk mengkonfigurasi alam semesta - ini adalah deduksi tentang sifat alam semesta.

ULONG_MAX persis sama. Itu disimpulkan / dihitung berdasarkan sifat nomor N-bit. Mengubahnya limits.hakan menjadi ide yang sangat buruk jika konstanta itu digunakan di suatu tempat dengan asumsi itu seharusnya mewakili realitas sistem .

Dan Anda tidak dapat mengubah kenyataan yang dipaksakan oleh perangkat keras Anda.


1. Saya tidak berpikir bahwa ini (alat representasi integer) sebenarnya dijamin oleh bash, karena tergantung pada pustaka C yang mendasarinya dan standar C tidak menjamin itu. Namun, inilah yang digunakan pada kebanyakan komputer modern normal.

goldilocks
sumber
Saya sangat berterima kasih! Datang untuk berdamai dengan gajah di dalam ruangan dan berpikir. Ya, di bagian pertama kebanyakan tentang kata-kata. Saya telah memperbarui Q untuk menunjukkan apa yang saya maksud. Saya akan meneliti mengapa komplemen dua menggambarkan beberapa apa yang saya lihat dan jawaban Anda sangat berharga dalam memahami itu! Sejauh menyangkut UNIX Q, saya pasti salah membaca sesuatu tentang ARG_MAX dengan AIX di sini . Bersulang!
1
Bahkan Anda bisa menggunakan komplemen dua untuk menentukan nilainya jika Anda yakin berada dalam kisaran> 2 * $max, seperti yang Anda gambarkan. Poin saya adalah 1) itu bukan tujuannya, 2) pastikan Anda mengerti jika Anda ingin melakukan itu, 3) itu tidak terlalu berguna karena penerapan yang sangat terbatas, 4) sesuai catatan kaki itu tidak benar-benar dijamin bahwa sistem tidak gunakan komplemen dua. Singkatnya, mencoba mengeksploitasi bahwa dalam kode program akan dianggap sebagai praktik yang sangat buruk. Ada pustaka / modul "angka besar" (untuk cangkang di bawah POSIX, bc) - gunakan jika perlu.
goldilocks
Baru-baru ini saya menyaksikan sesuatu yang memanfaatkan pelengkap keduanya untuk mengimplementasikan ALU dengan penambah biner 4-bit dengan IC carry cepat; bahkan ada perbandingan dengan komplemen seseorang (untuk melihat bagaimana itu). Penjelasan Anda sangat berperan dalam membuat saya dapat menyebutkan dan menghubungkan apa yang saya lihat di sini dengan apa yang dibahas dalam video - video itu , meningkatkan kemungkinan saya benar-benar dapat memahami semua implikasi di telepon setelah semuanya meresap. Terima kasih lagi untuk itu! Bersulang!