การเลื่อนระดับบิตและจำนวนเต็มที่ใหญ่ที่สุดใน Bash


16

นี่เป็นคำถามสำรวจซึ่งหมายความว่าฉันไม่แน่ใจว่าคำถามนี้เกี่ยวข้องกับอะไร แต่ฉันคิดว่ามันเป็นจำนวนเต็มที่ใหญ่ที่สุดใน Bash อย่างไรก็ตามฉันจะนิยามมันอย่างชัดแจ้ง

$ echo $((1<<8))
256

ฉันกำลังผลิตจำนวนเต็มโดยขยับบิต ฉันจะไปได้ไกลแค่ไหน?

$ echo $((1<<80000))
1

ไม่ไกลขนาดนี้ (1 คาดไม่ถึงและฉันจะกลับไปหามัน) แต่

$ echo $((1<<1022))
4611686018427387904

ยังคงเป็นบวก อย่างไรก็ตามไม่ใช่:

$ echo $((1<<1023))
-9223372036854775808

และก้าวไปอีกขั้นหนึ่ง

$ echo $((1<<1024))
1

ทำไมต้อง 1 และทำไมต่อไปนี้?

$ echo $((1<<1025))
2
$ echo $((1<<1026))
4

มีคนต้องการวิเคราะห์ซีรี่ส์นี้หรือไม่

UPDATE

เครื่องของฉัน:

$ uname -a
Linux tomas-Latitude-E4200 4.4.0-47-generic #68-Ubuntu SMP Wed Oct 26 19:39:52 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

-9223372036854775808 = 0xF333333333333334 นั่นเป็นเคสที่ดูตลก แน่นอน 4611686018427387904 = 0x4000000000000000 ฉันสงสัยว่าคุณกำลังกดปุ่มเพื่อดูจำนวนบิตที่จะเลื่อนไป ทำไมคุณถึงทำเช่นนี้?
CVn

6
@ MichaelKjörling For amusement ;-p

2
@ MichaelKjörlingไม่ไม่ใช่ -9223372036854775808 จะเป็น 0x8000000000000000 คุณออกจากตัวเลขสุดท้ายเมื่อทำการตรวจสอบ: -922337203685477580 จะเป็น 0xF333333333333334
hvd

คำตอบ:


27

ทุบตีใช้intmax_tตัวแปรสำหรับการทางคณิตศาสตร์ ในระบบของคุณมีความยาว 64 บิตดังนั้น:

$ echo $((1<<62))
4611686018427387904

ซึ่งเป็น

100000000000000000000000000000000000000000000000000000000000000

ใน binary (1 ตามด้วย 62 0s) เปลี่ยนอีกครั้ง:

$ echo $((1<<63))
-9223372036854775808

ซึ่งเป็น

1000000000000000000000000000000000000000000000000000000000000000

ใน binary (63 0s) ในส่วนเสริมของสอง

ในการรับจำนวนเต็มที่เป็นตัวแทนที่ใหญ่ที่สุดคุณต้องลบ 1:

$ echo $(((1<<63)-1))
9223372036854775807

ซึ่งเป็น

111111111111111111111111111111111111111111111111111111111111111

ในไบนารี

ตามที่ระบุไว้ในคำตอบของilkkachu การขยับจะใช้โมดูโล 64 ในซีพียูx86 64- บิต(ไม่ว่าจะใช้หรือ) ซึ่งอธิบายพฤติกรรมที่คุณเห็น:RCLSHL

$ echo $((1<<64))
1

$((1<<0))เทียบเท่ากับ ดังนั้น$((1<<1025))เป็น$((1<<1)), $((1<<1026))คือ$((1<<2))...

คุณจะได้พบกับคำจำกัดความของประเภทและคุณค่าสูงสุดstdint.h; ในระบบของคุณ:

/* Largest integral types.  */
#if __WORDSIZE == 64
typedef long int                intmax_t;
typedef unsigned long int       uintmax_t;
#else
__extension__
typedef long long int           intmax_t;
__extension__
typedef unsigned long long int  uintmax_t;
#endif

/* Minimum for largest signed integral type.  */
# define INTMAX_MIN             (-__INT64_C(9223372036854775807)-1)
/* Maximum for largest signed integral type.  */
# define INTMAX_MAX             (__INT64_C(9223372036854775807))

1
ไม่มีคุณต้องการพวกเขา Binary มีลำดับความสำคัญสูงกว่า- <<
cuonglm

1
@cuonglm huh ให้บริการฉันที่เหมาะสมสำหรับการทดสอบกับ zsh ... ขอบคุณอีกครั้ง!
Stephen Kitt

@cuonglm และ Stephen นั่นคือการแก้ไขที่ดี ให้ฉันecho $((1<<63-1)) 4611686018427387904

@tomas Yup, ทุบตีใช้ C มีความสำคัญผู้ประกอบการ zsh มีของตัวเองโดยเริ่มต้นที่เท่าเทียมกัน$((1<<63-1)) $(((1<<63)-1))
Stephen Kitt

นี่เป็นเรื่องดีที่จะรู้คำถามที่ดีและคำตอบของคุณขอบคุณทั้งคุณสตีเฟ่นคิตต์และโทมัส
Valentin B.

4

จากCHANGESไฟล์สำหรับbash2.05b:

J ตอนนี้เชลล์ดำเนินการทางคณิตศาสตร์ในขนาดจำนวนเต็มที่ใหญ่ที่สุดที่เครื่องรองรับ (intmax_t) แทนที่จะยาว

บนเครื่อง x86_64 intmax_tสอดคล้องกับเลขจำนวนเต็ม 64- บิตที่ลงชื่อแล้ว ดังนั้นคุณจะได้รับค่ามีความหมายระหว่างและ-2^63 2^63-1นอกช่วงที่คุณเพิ่งได้รับรอบ


Nitpick: ระหว่าง-2^63และ2^63-1รวม
สัตว์ที่กำหนด

4

เลื่อนลอย 1024 ให้หนึ่งเพราะจำนวนเงินที่กะจะมาได้อย่างมีประสิทธิภาพแบบโมดูโลจำนวนบิต (64) ดังนั้นและ1024 === 64 === 01025 === 65 === 1

การเปลี่ยนบางอย่างที่ไม่ใช่แบบ1ทำให้ชัดเจนว่าไม่ใช่การหมุนบิตเนื่องจากบิตที่สูงกว่าจะไม่พันไปทางต่ำสุดก่อนที่ค่าการเปลี่ยนแปลงจะเป็น (อย่างน้อย) 64:

$ printf "%x\n" $(( 5 << 63 )) $(( 5 << 64 ))
8000000000000000
5

อาจเป็นได้ว่าพฤติกรรมนี้ขึ้นอยู่กับระบบ รหัสทุบตีสตีเฟ่นที่เชื่อมโยงกับการแสดงเพียงแค่การเปลี่ยนแปลงธรรมดาโดยไม่มีการตรวจสอบใด ๆ สำหรับค่าขวามือ หากฉันจำได้อย่างถูกต้องตัวประมวลผล x86 จะใช้ค่ากะหกบิตด้านล่างเท่านั้น (ในโหมด 64 บิต) ดังนั้นพฤติกรรมอาจมาจากภาษาเครื่องโดยตรง นอกจากนี้ฉันคิดว่าการเปลี่ยนแปลงที่มากกว่าความกว้างของบิตไม่ได้กำหนดไว้อย่างชัดเจนใน C เช่นกัน ( gccเตือนสำหรับสิ่งนั้น)


2

สร้างจำนวนเต็มโดยขยับบิต ฉันจะไปได้ไกลแค่ไหน?

จนกระทั่งการเป็นตัวแทนจำนวนเต็มล้อมรอบ (ค่าเริ่มต้นในเชลล์ส่วนใหญ่)
64 2**63 - 1บิตจำนวนเต็มมักจะล้อมรอบที่
นั่นคือ0x7fffffffffffffffหรือ9223372036854775807ในเดือนธันวาคม

หมายเลขนั้น '+1' จะกลายเป็นลบ

นั่นเป็นเช่นเดียวกับ1<<63:

$ echo "$((1<<62)) $((1<<63)) and $((1<<64))"
4611686018427387904 -9223372036854775808 and 1

หลังจากนั้นกระบวนการจะทำซ้ำอีกครั้ง

$((1<<80000)) $((1<<1022)) $((1<<1023)) $((1<<1024)) $((1<<1025)) $((1<<1026))

ผลที่ได้ขึ้นอยู่กับmod 64ของมูลค่าขยับ[เป็น]

[a] จาก: คู่มือผู้พัฒนาซอฟท์แวร์สถาปัตยกรรมซอฟต์แวร์Intel® 64 และ IA-32: เล่มที่ 2จำนวนมาสก์เป็น 5 บิต (หรือ 6 บิตหากใช้โหมด 64 บิตและ REX.W) ช่วงการนับนั้น จำกัด ไว้ที่ 0 ถึง 31 (หรือ 63 ถ้าใช้โหมด 64 บิตและ REX.W) .

ยัง: จำไว้ว่า$((1<<0))เป็น1

$ for i in 80000 1022 1023 1024 1025 1026; do echo "$((i%64)) $((1<<i))"; done
 0 1
62 4611686018427387904
63 -9223372036854775808
 0 1
 1 2
 2 4

ดังนั้นทุกอย่างขึ้นอยู่กับว่าหมายเลขนั้นใกล้เคียงกับผลคูณของ 64

ทดสอบขีด จำกัด :

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

ทุบตี

ก่อนอื่นเราต้องการจำนวนเต็มที่มากที่สุดของแบบฟอร์ม2^n(1 bit set ตามด้วยศูนย์) เราสามารถทำได้โดยเลื่อนไปทางซ้ายจนกว่าการเปลี่ยนครั้งต่อไปจะทำให้จำนวนติดลบเรียกอีกอย่างว่า "ล้อมรอบ":

a=1;   while ((a>0));  do ((b=a,a<<=1))  ; done

bผลลัพธ์อยู่ที่ไหน: ค่าก่อนการเปลี่ยนครั้งล่าสุดที่ทำให้ลูปล้มเหลว

จากนั้นเราต้องลองทุกบิตเพื่อดูว่าอันไหนมีผลต่อสัญลักษณ์ของe:

c=$b;d=$b;
while ((c>>=1)); do
      ((e=d+c))
      (( e>0 )) && ((d=e))
done;
intmax=$d

จำนวนเต็มสูงสุด ( intmax) dเป็นผลมาจากค่าสุดท้ายของ

ทางด้านลบ (น้อยกว่า0) เราทำการทดสอบทั้งหมดซ้ำ แต่การทดสอบเมื่อสามารถทำ 0 ได้โดยไม่ต้องพัน

การทดสอบทั้งหมดพร้อมพิมพ์ทุกขั้นตอนคือ (สำหรับทุบตี):

#!/bin/bash
sayit(){ printf '%020d 0x%016x\n' "$1"{,}; }
a=1;       while ((a>0)) ; do((b=a,a<<=1))              ; sayit "$a"; done
c=$b;d=$b; while((c>>=1)); do((e=d+c));((e>0))&&((d=e)) ; sayit "$d"; done;
intmax=$d
a=-1;      while ((a<0)) ; do((b=a,a<<=1))              ; sayit "$b"; done;
c=$b;d=$b; while ((c<-1)); do((c>>=1,e=d+c));((e<0))&&((d=e)); sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

ดวลจุดโทษ

แปลเป็นเกือบทุกเชลล์:

#!/bin/sh
printing=false
sayit(){ "$printing" && printf '%020d 0x%016x\n' "$1" "$1"; }
a=1;       while [ "$a" -gt 0  ];do b=$a;a=$((a<<1)); sayit "$a"; done
c=$b;d=$b; while c=$((c>>1)); [ "$c" -gt 0 ];do e=$((d+c)); [ "$e" -gt 0 ] && d=$e ; sayit "$d"; done;
intmax=$d
a=-1;      while [ "$a" -lt 0  ];do b=$a;a=$((a<<1)); sayit "$b"; done;
c=$b;d=$b; while [ "$c" -lt -1 ];do c=$((c>>1));e=$((d+c));[ "$e" -lt 0 ] && d=$e ; sayit "$d"; done
intmin=$d       

printf '%20d max positive value 0x%016x\n' "$intmax" "$intmax"
printf '%20d min negative value 0x%016x\n' "$intmin" "$intmin"

ทำงานข้างต้นสำหรับเชลล์จำนวนมากค่า
ทั้งหมด (ยกเว้น bash 2.04 และ mksh) ยอมรับได้ถึง ( 2**63 -1) ในคอมพิวเตอร์เครื่องนี้

เป็นที่น่าสนใจที่จะรายงานว่าเปลือก att :

$ attsh --version
version         sh (AT&T Research) 93u+ 2012-08-01

พิมพ์ข้อผิดพลาดเกี่ยวกับค่าที่$((2^63))ไม่ใช่ ksh

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.