Pergeseran bitwise dan integer terbesar di Bash

16

Ini adalah pertanyaan eksplorasi, artinya saya tidak sepenuhnya yakin tentang pertanyaan ini, tapi saya pikir ini tentang bilangan bulat terbesar di Bash. Bagaimanapun, saya akan mendefinisikannya secara berlebihan.

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

Saya menghasilkan bilangan bulat dengan menggeser sedikit. Seberapa jauh saya bisa pergi?

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

Sepertinya tidak sejauh ini. (1 tidak terduga, dan saya akan kembali ke sana.) Tapi,

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

masih positif. Namun, bukan ini:

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

Dan satu langkah lebih jauh,

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

Kenapa 1? Dan mengapa berikut ini?

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

Apakah seseorang ingin menganalisis seri ini?

MEMPERBARUI

Mesin saya:

$ 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
Gilles 'SO- berhenti menjadi jahat'
sumber
-9223372036854775808 = 0xF333333333333334. Itu kasus tepi yang terlihat lucu. Tentu saja, 4611686018427387904 = 0x400000000000000000. Saya menduga Anda memukul beberapa jenis pada jumlah bit untuk bergeser. Kenapa kau melakukan ini?
CVn
6
@ MichaelKjörling Untuk hiburan ;-p
2
@ MichaelKjörling Tidak, tidak. -9223372036854775808 akan menjadi 0x8000000000000000000. Anda meninggalkan digit terakhir saat memeriksa: -922337203685477580 akan menjadi 0xF333333333333334.
hvd

Jawaban:

27

Bash menggunakan intmax_tvariabel untuk aritmatika . Di sistem Anda panjangnya 64 bit, jadi:

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

yang mana

100000000000000000000000000000000000000000000000000000000000000

dalam biner (1 diikuti oleh 62 0s). Alihkan lagi:

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

yang mana

1000000000000000000000000000000000000000000000000000000000000000

dalam biner (63 0s), dalam aritmatika komplemen dua.

Untuk mendapatkan integer representable terbesar, Anda harus mengurangi 1:

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

yang mana

111111111111111111111111111111111111111111111111111111111111111

dalam biner.

Seperti keluar menunjuk di ilkkachu 's jawaban , menggeser mengambil offset modulo 64 pada 64-bit x86 CPU (apakah menggunakan RCLatau SHL), yang menjelaskan perilaku yang Anda lihat:

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

setara dengan $((1<<0)). Dengan demikian $((1<<1025))adalah $((1<<1)), $((1<<1026))adalah $((1<<2))...

Anda akan menemukan definisi jenis dan nilai maksimum di stdint.h; di sistem Anda:

/* 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))
Stephen Kitt
sumber
1
Tidak, Anda membutuhkannya, Biner -memiliki prioritas lebih tinggi daripada <<.
cuonglm
1
@cuonglm huh, melayani saya tepat untuk pengujian di zsh ... Terima kasih lagi!
Stephen Kitt
@cuonglm dan Stephen. Ya, itu hasil edit yang bagus. echo $((1<<63-1))memberi saya 4611686018427387904.
@tomas ya, bash menggunakan diutamakan operator C, zsh memiliki default sendiri di mana $((1<<63-1))sama dengan $(((1<<63)-1)).
Stephen Kitt
Ini bagus untuk diketahui, pertanyaan yang bagus dan jawaban yang sangat bagus, terima kasih kepada Anda berdua Stephen Kitt dan tomas.
Valentin B.
4

Dari CHANGESfile untuk bash2.05b:

j. Shell sekarang melakukan aritmatika dalam ukuran bilangan bulat terbesar yang didukung mesin (intmax_t), bukan panjang.

Pada mesin x86_64 intmax_tsesuai dengan integer 64-bit yang ditandatangani. Jadi, Anda mendapatkan nilai yang berarti antara -2^63dan 2^63-1. Di luar rentang itu Anda hanya mendapatkan wrap-arounds.

Satō Katsura
sumber
Nitpick: antara -2^63dan 2^63-1, inklusif.
Hewan Nominal
4

Pergeseran oleh 1024 memberikan satu, karena jumlah pergeseran secara efektif diambil modulo jumlah bit (64), jadi 1024 === 64 === 0, dan 1025 === 65 === 1.

Menggeser sesuatu selain a 1memperjelas bahwa itu bukan rotasi bit, karena bit yang lebih tinggi tidak membungkus ke bagian bawah sebelum nilai shiftnya (setidaknya) 64:

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

Mungkin perilaku ini tergantung pada sistem. The kode pesta Stephen terkait dengan menunjukkan hanya pergeseran polos, tanpa memeriksa untuk nilai tangan kanan. Jika saya ingat dengan benar, prosesor x86 hanya menggunakan enam bit terbawah dari nilai shift (dalam mode 64-bit), sehingga perilaku mungkin langsung dari bahasa mesin. Juga, saya pikir bergeser lebih dari lebar bit tidak didefinisikan dengan jelas dalam C baik ( gccmemperingatkan untuk itu).

ilkkachu
sumber
2

menghasilkan bilangan bulat dengan menggeser sedikit. Seberapa jauh saya bisa pergi?

Sampai representasi integer membungkus (default di kebanyakan shell).
Bilangan bulat 64 bit biasanya membungkus di 2**63 - 1.
Itu 0x7fffffffffffffffatau9223372036854775807 di bulan Desember.

Angka '+1' menjadi negatif.

Itu sama dengan 1<<63, jadi:

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

Setelah itu prosesnya berulang lagi.

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

Hasilnya tergantung pada mod 64nilai pergeseran [a] .

[a] Dari: Manual Pengembang Perangkat Lunak Arsitektur Intel® 64 dan IA-32: Volume 2 Hitungannya disamarkan menjadi 5 bit (atau 6 bit jika dalam mode 64-bit dan REX.W digunakan). Kisaran hitungan dibatasi hingga 0 hingga 31 (atau 63 jika mode 64-bit dan REX.W digunakan). .

Juga: ingat bahwa $((1<<0))adalah1

$ 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

Jadi, itu semua tergantung pada seberapa dekat angka ke kelipatan 64.

Menguji batas:

Cara kuat untuk menguji yang merupakan bilangan bulat positif (dan negatif) maksimum adalah dengan menguji setiap bit pada gilirannya. Itu kurang dari 64 langkah untuk sebagian besar komputer, itu tidak akan terlalu lambat.

pesta

Pertama kita membutuhkan bilangan bulat terbesar dari bentuk 2^n(set 1 bit diikuti oleh nol). Kita dapat melakukannya dengan menggeser ke kiri sampai shift berikutnya membuat angka negatif, juga disebut "membungkus":

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

Di mana bhasilnya: nilai sebelum shift terakhir yang gagal dalam loop.

Maka kita perlu mencoba setiap bit untuk mencari tahu mana yang mempengaruhi tanda e:

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

Integer maksimum ( intmax) dihasilkan dari nilai terakhir dari d.

Di sisi negatif (kurang dari 0) kami mengulangi semua tes tetapi menguji kapan bit bisa dibuat 0 tanpa melilit.

Seluruh pengujian dengan pencetakan semua langkah adalah ini (untuk bash):

#!/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"

SH

Diterjemahkan ke hampir semua shell:

#!/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"

Menjalankan hal di atas untuk banyak shell,
semua (kecuali bash 2.04 dan mksh) menerima nilai hingga ( 2**63 -1) di komputer ini.

Sangat menarik untuk melaporkan bahwa shell att :

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

mencetak kesalahan pada nilai $((2^63)), bukan ksh sekalipun.

sorontar
sumber