Sự dịch chuyển bitwise và số nguyên lớn nhất trong Bash


16

Đây là một câu hỏi thăm dò, có nghĩa là tôi không hoàn toàn chắc chắn câu hỏi này là gì, nhưng tôi nghĩ đó là về số nguyên lớn nhất trong Bash. Dù sao đi nữa, tôi sẽ định nghĩa nó một cách phô trương.

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

Tôi đang tạo ra một số nguyên bằng cách thay đổi một chút. Tôi có thể đi bao xa?

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

Không phải điều này xa, rõ ràng. (1 là bất ngờ, và tôi sẽ quay lại với nó.) Nhưng,

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

vẫn tích cực. Không phải cái này, tuy nhiên:

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

Và một bước xa hơn,

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

Tại sao 1? Và tại sao sau đây?

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

Ai đó muốn phân tích loạt bài này?

CẬP NHẬT

Máy của tôi:

$ 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 = 0xF3333333333333334. Đó là một trường hợp cạnh trông buồn cười. Tất nhiên, 4611686018427387904 = 0x4000000000000000. Tôi nghi ngờ bạn đang đánh vào một số loại của số bit để thay đổi. Tại sao bạn làm điều này, dù sao?
một CVn

6
@ MichaelKjorling Để giải trí ;-p
Tomasz

2
@ MichaelKjorling Không, không phải vậy. -9223372036854775808 sẽ là 0x8000000000000000. Bạn để lại chữ số cuối cùng khi kiểm tra: -922337203685477580 sẽ là 0xF333333333333334.
hvd

Câu trả lời:


27

Bash sử dụng intmax_tcác biến cho số học . Trên hệ thống của bạn có độ dài 64 bit, vì vậy:

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

đó là

100000000000000000000000000000000000000000000000000000000000000

ở dạng nhị phân (1 theo sau là 62 0). Thay đổi điều đó một lần nữa:

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

đó là

1000000000000000000000000000000000000000000000000000000000000000

trong nhị phân (63 0 giây), trong số học bổ sung của hai.

Để có được số nguyên đại diện lớn nhất, bạn cần trừ 1:

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

đó là

111111111111111111111111111111111111111111111111111111111111111

trong nhị phân.

Như đã chỉ ra trong câu trả lời của ilkkachu , việc dịch chuyển lấy modulo 64 bù trên các CPU x86 64 bit (cho dù sử dụng hay ), giải thích hành vi bạn đang thấy:RCLSHL

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

tương đương với $((1<<0)). Như vậy $((1<<1025))$((1<<1)), $((1<<1026))$((1<<2))...

Bạn sẽ tìm thấy các định nghĩa loại và giá trị tối đa trong stdint.h; trên hệ thống của bạn:

/* 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
Không, bạn cần chúng, Nhị phân -có quyền ưu tiên cao hơn <<.
cuonglm

1
@cuonglm huh, phục vụ tôi ngay để thử nghiệm trên zsh ... Cảm ơn một lần nữa!
Stephen Kitt

@cuonglm và Stephen. Vâng, đó là một chỉnh sửa tốt. echo $((1<<63-1))mang lại cho tôi 4611686018427387904.
Tomasz

@tomas yup, bash sử dụng ưu tiên toán tử C, zsh có mặc định của riêng nó trong đó $((1<<63-1))bằng $(((1<<63)-1)).
Stephen Kitt

Đây là điều tốt để biết, một câu hỏi hay và một câu trả lời rất hay, cảm ơn cả Stephen Kitt và tomas.
Valentin B.

4

Từ CHANGEStệp cho bash2.05b:

j. Shell bây giờ thực hiện số học ở kích thước nguyên lớn nhất mà máy hỗ trợ (intmax_t), thay vì dài.

Trên máy x86_64 intmax_ttương ứng với số nguyên 64 bit đã ký. Vì vậy, bạn có được giá trị có ý nghĩa giữa -2^632^63-1. Bên ngoài phạm vi đó bạn chỉ cần có được bọc xung quanh.


Nitpick: giữa -2^632^63-1, bao gồm.
Động vật danh nghĩa

4

Sự thay đổi của 1024 mang lại một, bởi vì lượng dịch chuyển được lấy một cách hiệu quả modulo số lượng bit (64), vì vậy 1024 === 64 === 0, và 1025 === 65 === 1.

Dịch chuyển một cái gì đó khác hơn là 1làm cho nó rõ ràng không phải là một bit xoay, vì các bit cao hơn không bao quanh đến mức thấp trước khi giá trị thay đổi là (ít nhất là) 64:

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

Nó có thể là hành vi này phụ thuộc vào hệ thống. Các đang bash Stephen liên quan đến chương trình chỉ là một sự thay đổi đơn giản, mà không cần bất kỳ kiểm tra cho giá trị tay phải. Nếu tôi nhớ chính xác, bộ xử lý x86 chỉ sử dụng sáu bit dưới cùng của giá trị dịch chuyển (ở chế độ 64 bit), do đó hành vi có thể trực tiếp từ ngôn ngữ máy. Ngoài ra, tôi nghĩ rằng dịch chuyển nhiều hơn độ rộng bit không được xác định rõ ràng trong C ( gcccảnh báo cho điều đó).


2

tạo ra một số nguyên bằng cách dịch chuyển một chút. Tôi có thể đi bao xa?

Cho đến khi biểu diễn số nguyên bao bọc xung quanh (mặc định trong hầu hết các shell).
Một số nguyên 64 bit thường bao quanh tại 2**63 - 1.
Đó là 0x7fffffffffffffffhoặc 9223372036854775807trong tháng mười hai.

Con số '+1' trở thành âm.

Điều đó cũng giống như 1<<63, do đó:

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

Sau đó, quá trình lặp lại một lần nữa.

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

Kết quả phụ thuộc vào mod 64giá trị dịch chuyển [a] .

[a] Từ: Hướng dẫn dành cho nhà phát triển phần mềm Intel® 64 và IA-32 Architectures: Tập 2 Đếm được che dấu thành 5 bit (hoặc 6 bit nếu ở chế độ 64 bit và REX.W được sử dụng). Phạm vi đếm được giới hạn từ 0 đến 31 (hoặc 63 nếu sử dụng chế độ 64 bit và REX.W). .

Ngoài ra: hãy nhớ rằng đó $((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

Vì vậy, tất cả phụ thuộc vào mức độ gần với số bội của 64.

Kiểm tra giới hạn:

Cách mạnh mẽ để kiểm tra số nguyên dương (và âm) tối đa là kiểm tra lần lượt từng bit một. Dù chưa đến 64 bước đối với hầu hết các máy tính, nó sẽ không quá chậm.

bash

Đầu tiên chúng ta cần số nguyên lớn nhất của biểu mẫu 2^n(tập 1 bit theo sau là số không). Chúng ta có thể làm điều đó bằng cách dịch chuyển sang trái cho đến khi sự thay đổi tiếp theo làm cho số âm, còn được gọi là "bao quanh":

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

Trong trường hợp blà kết quả: giá trị trước sự thay đổi cuối cùng thất bại vòng lặp.

Sau đó, chúng ta cần cố gắng từng chút một để tìm ra cái nào ảnh hưởng đến dấu hiệu của e:

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

Số nguyên tối đa ( intmax) kết quả từ giá trị cuối cùng củad .

Về mặt tiêu cực (ít hơn 0 ), chúng tôi lặp lại tất cả các thử nghiệm nhưng kiểm tra khi một bit có thể được thực hiện 0 mà không bao quanh.

Toàn bộ bài kiểm tra với việc in tất cả các bước là thế này (đối với 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

Được dịch sang hầu hết mọi 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"

Chạy ở trên cho nhiều shell,
tất cả (ngoại trừ bash 2.04 và mksh) giá trị được chấp nhận lên đến (2**63 -1 ) trong máy tính này.

Thật thú vị khi báo cáo rằng vỏ att :

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

in một lỗi trên các giá trị của $((2^63)), mặc dù không phải ksh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.