Ada batas yang ditetapkan untuk kemampuan evaluasi aritmatika dari bash
shell. 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.h
cara 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 bash
sumber 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 if
pernyataan dan saya dapat mencari perintah seperti awk
juga dll. Saya melihat ekspresi reguler yang saya gunakan tidak menangkap apa pun tentang alat presisi sewenang-wenang yang saya miliki seperti bc
dan dc
.
Pertanyaan
- Apa alasan untuk tidak memperingatkan Anda (seperti
awk
halnya 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? - Saya telah membaca bahwa rasa UNIX dapat mengubah ULONG_MAX secara interaktif? Adakah yang pernah mendengar ini?
- Jika seseorang secara sewenang-wenang mengubah nilai maksimum integer unsigned
limits.h
, lalu mengkompilasi ulangbash
, 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 ...
bc
, misalnya:$num=$(echo 6^6^6 | bc)
. Sayangnya,bc
menempatkan jeda baris, jadi Anda harusnum=$(echo $num | sed 's/\\\s//g')
sesudahnya; jika Anda melakukannya di dalam pipa, ada karakter baris baru yang sebenarnya, yang canggung dengan sed, meskipunnum=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
berfungsi. Dalam kedua kasus Anda sekarang memiliki bilangan bulat yang dapat digunakan, misalnyanum2=$(echo "$num * 2" | bc)
,.bc
dengan mengaturBC_LINE_LENGTH=0
.Jawaban:
Tidak. Bagaimana menurut Anda? Dengan contoh Anda sendiri, maks adalah:
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:
Mungkin maksud Anda ini adalah angka yang dapat Anda tambahkan
$max
untuk mendapatkan perbedaan negatif, karena:Tapi ini sebenarnya tidak terus berlaku:
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):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
bash
mematuhi 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.
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.h
akan 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.sumber
$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.