เหตุผลสำหรับเปลือกทุบตีไม่เตือนคุณเกี่ยวกับการคำนวณทางคณิตศาสตร์ล้น ฯลฯ คืออะไร?


9

มีขีด จำกัด สำหรับความสามารถในการประเมินผลทางคณิตศาสตร์ของbashเชลล์ คู่มือฉบับย่อเกี่ยวกับแง่มุมของเชลล์ทางคณิตศาสตร์นี้ แต่ระบุว่า :

การประเมินจะทำในจำนวนเต็มที่มีความกว้างคงที่โดยไม่มีการตรวจสอบการล้น แต่การหารด้วย 0 จะถูกดักและตั้งค่าสถานะเป็นข้อผิดพลาด โอเปอเรเตอร์และลำดับความสำคัญการเชื่อมโยงและค่าต่าง ๆ นั้นเหมือนกับในภาษา C

จำนวนเต็มที่มีความกว้างคงที่ซึ่งหมายถึงนี้เป็นจริงเกี่ยวกับชนิดข้อมูลที่ใช้ (และเฉพาะของสาเหตุนี้เกินกว่านี้) แต่ค่าขีด จำกัด จะแสดง/usr/include/limits.hในแบบนี้:

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

และเมื่อคุณรู้ว่าคุณสามารถยืนยันสถานะของความจริงเช่นนี้:

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

นี่เป็นจำนวนเต็ม 64 บิตและนี่แปลโดยตรงในเชลล์ในบริบทของการประเมินทางคณิตศาสตร์:

# 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

ดังนั้นระหว่าง 2 63 2 64 -1 คุณจะได้รับจำนวนเต็มเชิงลบแสดงวิธีห่างไกลจาก ULONG_MAX คุณเป็น1 เมื่อการประเมินถึงขีด จำกัด นั้นและมากเกินไปคุณจะไม่ได้รับคำเตือนใด ๆ และลำดับของการประเมินนั้นจะถูกรีเซ็ตเป็น 0 ซึ่งอาจทำให้เกิดพฤติกรรมที่ผิดปกติบางอย่างเช่นการยกกำลังเชื่อมโยงทางขวาเช่น:

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

การใช้sh -c 'command'ไม่ได้เปลี่ยนแปลงอะไรดังนั้นฉันต้องสมมติว่านี่เป็นเรื่องปกติและเป็นไปตามมาตรฐาน ตอนนี้ฉันคิดว่าฉันมีความเข้าใจพื้นฐาน แต่เป็นรูปธรรมเกี่ยวกับช่วงและขีด จำกัด ทางคณิตศาสตร์และความหมายในเชลล์สำหรับการประเมินการแสดงออกฉันคิดว่าฉันสามารถมองอย่างรวดเร็วว่าข้อมูลประเภทใดที่ซอฟต์แวร์อื่นใช้ใน Linux ฉันใช้bashแหล่งข้อมูลบางอย่างเพื่อเพิ่มอินพุตของคำสั่งนี้:

{ 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งบและฉันสามารถค้นหาคำสั่งเช่นawkฯลฯ ฉันแจ้งให้ทราบล่วงหน้าเกินไปการแสดงออกปกติผมใช้ไม่ได้จับอะไรเกี่ยวกับเครื่องมือความแม่นยำโดยพลฉันมีเช่นและbcdc


คำถาม

  1. อะไรคือเหตุผลที่ไม่เตือนคุณ (เช่นเดียวกับawkเมื่อประเมิน 2 ^ 1024) เมื่อการประเมินทางคณิตศาสตร์ของคุณล้น ทำไมจำนวนเต็มลบระหว่าง 2 63และ 2 64 -1 ถึงผู้ใช้ปลายทางเมื่อเขาประเมินบางอย่าง
  2. ฉันได้อ่านที่ไหนซักแห่งที่ยูนิกซ์บางรสชาติสามารถเปลี่ยน ULONG_MAX แบบโต้ตอบได้? มีใครเคยได้ยินเรื่องนี้บ้างไหม?
  3. หากมีคนเปลี่ยนค่าจำนวนเต็มสูงสุดโดยไม่ได้ลงนามlimits.hแล้วคอมไพล์อีกbashครั้งสิ่งที่เราคาดหวังจะเกิดขึ้น?

บันทึก

1. ฉันต้องการแสดงให้เห็นชัดเจนยิ่งขึ้นในสิ่งที่ฉันเห็นเนื่องจากเป็นสิ่งเชิงประจักษ์ที่ง่ายมาก สิ่งที่ฉันสังเกตเห็นคือ:

  • (a) การประเมินใด ๆ ที่ให้ <2 ^ 63-1 ถูกต้อง
  • (b) การประเมินใด ๆ ที่ให้ => 2 ^ 63 สูงสุด 2 ^ 64 ให้จำนวนเต็มลบ:
    • ช่วงของจำนวนเต็มนั้นคือ x ถึง y x = -9223372036854775808 และ y = 0

เมื่อพิจารณาถึงสิ่งนี้การประเมินที่เป็นเช่น (b) สามารถแสดงเป็น 2 ^ 63-1 บวกบางอย่างภายใน x..y ตัวอย่างเช่นหากเราถูกขอให้ประเมิน (2 ^ 63-1) +100 002 (แต่อาจเป็นจำนวนที่น้อยกว่าใน (a)) เราจะได้รับ -9223372036854675807 ฉันแค่บอกชัดเจนว่าฉันเดา แต่นี่ก็หมายความว่าทั้งสองแสดงออกต่อไปนี้:

  • (2 ^ 63-1) + 100 002 และ;
  • (2 ^ 63-1) + (LLONG_MAX - {สิ่งที่เชลล์มอบให้เรา ((2 ^ 63-1) + 100 002) ซึ่งคือ -9223372036854675807} ดีโดยใช้ค่าบวกที่เรามี
    • (2 ^ 63-1) + (9223372036854775807 - 922337203685464680807 = 100 000)
    • = 9223372036854775807 + 100 000

สนิทกันมาก นิพจน์ที่สองคือ "2" นอกเหนือจาก (2 ^ 63-1) + 100 002 นั่นคือสิ่งที่เรากำลังประเมิน นี่คือสิ่งที่ฉันหมายถึงเมื่อคุณได้รับจำนวนเต็มลบแสดงว่าคุณอยู่ห่างจาก 2 ^ 64 มากแค่ไหน ฉันหมายความว่าด้วยจำนวนเต็มลบและความรู้เกี่ยวกับขีด จำกัด คุณไม่สามารถประเมินได้ภายในช่วง x ..y ใน bash shell แต่คุณสามารถไปที่อื่น - ข้อมูลสามารถใช้งานได้ถึง 2 ^ 64 ในแง่นั้น (ฉันสามารถเพิ่ม ขึ้นบนกระดาษหรือใช้เป็น bc) นอกเหนือจากนั้นพฤติกรรมจะคล้ายกับ 6 ^ 6 ^ 6 ถึงขีด จำกัด ดังที่อธิบายไว้ด้านล่างใน Q ...


5
ฉันเดาว่าเหตุผลจะทำให้ "กระสุนไม่ใช่เครื่องมือที่เหมาะสมสำหรับคณิตศาสตร์" มันไม่ได้ออกแบบมาสำหรับมันและไม่พยายามที่จะจัดการกับมันอย่างสง่างามตามที่คุณแสดง นรกกระสุนส่วนใหญ่ไม่ได้จัดการกับลอย!
terdon

@terdon ถึงแม้ว่าวิธีการที่เชลล์จะจัดการกับตัวเลขในกรณีนี้เหมือนกับภาษาระดับสูงทุกภาษาที่ฉันเคยได้ยินมา ประเภทจำนวนเต็มมีขนาดคงที่และสามารถล้นได้
goldilocks

@terdon แท้จริงแล้วขณะที่ฉันค้นคว้าสิ่งนี้ตั้งแต่เวลา 6 ^ 6 ^ 6 QI มารู้ว่า ฉันเดาด้วยว่าทำไมฉันไม่พบเนื้อหามากนักเพราะสิ่งนี้เกี่ยวข้องกับ C หรือแม้แต่ C99 ในฐานะที่ฉันไม่ใช่นักพัฒนาหรือบุคคลด้านไอทีฉันจึงต้องทำความเข้าใจกับความรู้ทั้งหมดที่เป็นพื้นฐานของสมมติฐานเหล่านี้ แน่นอนคนที่ต้องการความแม่นยำโดยพลการรู้เกี่ยวกับชนิดข้อมูล แต่เห็นได้ชัดว่าฉันไม่ใช่คนนั้น :) (แต่ฉันสังเกตเห็นพฤติกรรมของ awk @ 2 ^ 53 + 1 เช่น float double เพียงความแม่นยำและภายในเทียบกับการพิมพ์เป็นต้น !)

1
หากคุณต้องการที่จะทำงานกับตัวเลขใหญ่ในเปลือกใช้เช่น:bc $num=$(echo 6^6^6 | bc)แต่น่าเสียดายที่bcวางในบรรทัดแบ่งดังนั้นคุณต้องnum=$(echo $num | sed 's/\\\s//g')หลังจากนั้น ถ้าคุณทำมันในไพพ์มีอักขระขึ้นบรรทัดใหม่จริงซึ่งอึดอัดใจกับ sed แม้ว่าจะnum=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')ใช้ได้ num2=$(echo "$num * 2" | bc)ในทั้งสองกรณีขณะนี้คุณมีจำนวนเต็มซึ่งสามารถนำมาใช้เช่นการ
goldilocks

1
... ใครบางคนที่นี่ชี้ให้เห็นคุณสามารถปิดใช้งานคุณลักษณะแบ่งบรรทัดนี้โดยการตั้งค่าbc BC_LINE_LENGTH=0
goldilocks

คำตอบ:


11

ระหว่าง 2 ^ 63 และ 2 ^ 64-1 คุณจะได้จำนวนเต็มลบแสดงว่าคุณอยู่ห่างจาก ULONG_MAX มากแค่ไหน

ฉบับที่คุณจะคิดว่า? ตามตัวอย่างของคุณเองค่าสูงสุดคือ:

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

หาก "ล้น" หมายถึง "คุณจะได้รับจำนวนเต็มลบที่แสดงว่าคุณอยู่ห่างจาก ULONG_MAX มากเพียงใด" ถ้าคุณเพิ่มหนึ่งในนั้นคุณควรได้ -1 หรือไม่ แต่แทนที่จะ:

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

บางทีคุณอาจหมายถึงนี่คือตัวเลขที่คุณสามารถเพิ่ม$maxเพื่อรับผลต่างเชิงลบเนื่องจาก:

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

แต่ในความเป็นจริงมันไม่ได้เป็นจริง:

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

นี่เป็นเพราะระบบใช้ส่วนเติมเต็มของสองเพื่อใช้จำนวนเต็มลงนาม 1 ค่าที่เป็นผลมาจากการโอเวอร์โฟลว์ไม่ใช่ความพยายามที่จะให้ความแตกต่างความแตกต่างเชิงลบและอื่น ๆ ซึ่งเป็นผลมาจากการตัดค่าเป็นจำนวนบิตที่ จำกัด จากนั้นจึงตีความว่าเป็นจำนวนเต็มแบบเต็ม . ตัวอย่างเช่นเหตุผล$(($max + 1 + $max))ออกมาเป็น -1 เนื่องจากค่าสูงสุดในส่วนเติมเต็มของสองคือบิตทั้งหมดตั้งยกเว้นบิตสูงสุด (ซึ่งบ่งชี้เชิงลบ); การเพิ่มเหล่านี้เข้าด้วยกันโดยทั่วไปหมายถึงการถือบิตทั้งหมดไปทางซ้ายดังนั้นคุณจึงจบลงด้วย (ถ้าขนาดเป็น 16 บิตและไม่ใช่ 64):

11111111 11111110

บิตสูง (เครื่องหมาย) ได้รับการตั้งค่าแล้วเนื่องจากถูกยกไปเพิ่มเติม หากคุณเพิ่มอีกหนึ่ง (00000000 00000001) ลงไปคุณจะต้องตั้งค่าบิตทั้งหมดซึ่งในส่วนเติมเต็มของสองคือ -1

ฉันคิดว่าบางส่วนตอบคำถามในช่วงครึ่งหลังของคุณ - "ทำไมจำนวนเต็มลบ ... แสดงต่อผู้ใช้ปลายทาง" ครั้งแรกเพราะนั่นคือค่าที่ถูกต้องตามกฎของหมายเลขส่วนประกอบ 64 บิตสอง นี่คือการปฏิบัติตามแบบฉบับของภาษาการเขียนโปรแกรมระดับสูงส่วนใหญ่ (อื่น ๆ ) วัตถุประสงค์ทั่วไป (ฉันไม่สามารถนึกถึงภาษาที่ไม่ได้ทำสิ่งนี้) ดังนั้นจึงbashเป็นไปตามการประชุม ซึ่งเป็นคำตอบของส่วนแรกของคำถามแรก - "เหตุผลคืออะไร": นี่คือบรรทัดฐานในสเปคของภาษาการเขียนโปรแกรม

WRT คำถามที่ 2 ฉันไม่เคยได้ยินระบบที่เปลี่ยนแปลง ULONG_MAX แบบโต้ตอบ

หากใครบางคนเปลี่ยนค่าของจำนวนเต็มสูงสุดที่ไม่ได้ลงนามในขีด จำกัด แล้วทำการคอมไพล์ bash เราคาดหวังอะไรจะเกิดขึ้น

มันจะไม่สร้างความแตกต่างใด ๆ กับการคำนวณทางคณิตศาสตร์เนื่องจากนี่ไม่ใช่ค่าที่กำหนดเองที่ใช้เพื่อกำหนดค่าระบบ - เป็นค่าความสะดวกสบายที่เก็บค่าคงที่ไม่เปลี่ยนรูปที่สะท้อนฮาร์ดแวร์ โดยการเปรียบเทียบคุณสามารถกำหนดcเป็น 55 ไมล์ต่อชั่วโมง แต่ความเร็วของแสงจะยังคงอยู่ที่ 186,000 ไมล์ต่อวินาที cไม่ใช่ตัวเลขที่ใช้กำหนดค่าจักรวาล - มันเป็นการอนุมานเกี่ยวกับธรรมชาติของจักรวาล

ULONG_MAX เหมือนกันทุกประการ มันจะอนุมาน / คำนวณตามลักษณะของตัวเลข N-bit การเปลี่ยนแปลงในlimits.hจะเป็นความคิดที่ดีมากถ้าคงที่ที่ใช้ในที่ใดที่หนึ่งสมมติว่ามันควรจะเป็นตัวแทนของความเป็นจริงของระบบ

และคุณไม่สามารถเปลี่ยนความเป็นจริงที่กำหนดโดยฮาร์ดแวร์ของคุณ


1. ฉันไม่คิดว่าสิ่งนี้ (หมายถึงการแสดงจำนวนเต็ม) ได้รับการรับรองจริง ๆbashเพราะมันขึ้นอยู่กับไลบรารี C พื้นฐานและ C มาตรฐานไม่รับประกันว่า อย่างไรก็ตามนี่คือสิ่งที่ใช้กับคอมพิวเตอร์สมัยใหม่ส่วนใหญ่


ฉันขอบคุณมาก! ตกลงกับช้างในห้องและกำลังคิด ใช่ในส่วนแรกมันเกี่ยวกับคำเป็นส่วนใหญ่ ฉันได้อัปเดต Q เพื่อแสดงความหมายของฉัน ฉันจะค้นคว้าว่าทำไมส่วนประกอบสองอย่างอธิบายถึงสิ่งที่ฉันเห็นและคำตอบของคุณมีค่าในการทำความเข้าใจสิ่งนั้น! เท่าที่ใช้ระบบปฏิบัติการยูนิกซ์ Q เป็นห่วงฉันจะต้องมีบางสิ่งบางอย่างเกี่ยวกับการอ่านผิด ARG_MAX กับ AIX ที่นี่ ไชโย!

1
ในความเป็นจริงคุณสามารถใช้ส่วนประกอบสองอย่างเพื่อกำหนดค่าหากคุณแน่ใจว่าอยู่ในช่วง> 2 * $maxตามที่คุณอธิบาย คะแนนของฉันคือ 1) นั่นไม่ใช่จุดประสงค์ 2) ตรวจสอบให้แน่ใจว่าคุณเข้าใจถ้าคุณต้องการที่จะทำ 3) มันไม่ได้มีประโยชน์มากเพราะการบังคับใช้ที่ จำกัด มาก 4) ตามเชิงอรรถมันไม่ได้รับประกันจริง ๆ ว่าระบบ ใช้ส่วนประกอบสองอย่าง กล่าวโดยย่อคือการพยายามใช้ประโยชน์จากรหัสโปรแกรมจะถือว่าเป็นการปฏิบัติที่แย่มาก มีไลบรารี / โมดูล "จำนวนมาก" (สำหรับเชลล์ภายใต้ POSIX, bc) - ใช้สิ่งเหล่านี้หากคุณต้องการ
goldilocks

เมื่อไม่นานมานี้ฉันได้เห็นบางสิ่งบางอย่างที่ใช้ประโยชน์จากทั้งสองอย่างในการติดตั้ง ALU ด้วย adder แบบไบนารีขนาด 4 บิตที่มี IC พกพาที่รวดเร็ว แม้จะมีการเปรียบเทียบกับส่วนประกอบ (เพื่อดูว่ามันเป็น) คำอธิบายของคุณเป็นเครื่องมือในการที่ฉันสามารถตั้งชื่อและเชื่อมต่อสิ่งที่ฉันเห็นที่นี่กับสิ่งที่ถูกกล่าวถึงในวิดีโอเหล่านั้นเพิ่มโอกาสที่ฉันอาจเข้าใจความหมายทั้งหมดลงในบรรทัดเมื่อทุกอย่างจมลง ไชโย!
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.