Có các giới hạn được đặt cho các khả năng đánh giá số học của hệ bash
vỏ. Hướng dẫn ngắn gọn về khía cạnh này của số học shell nhưng nêu rõ :
Đánh giá được thực hiện trong các số nguyên có chiều rộng cố định mà không kiểm tra tràn, mặc dù phép chia cho 0 bị kẹt và bị gắn cờ là lỗi. Các toán tử và mức độ ưu tiên, kết hợp và giá trị của chúng giống như trong ngôn ngữ C.
Số nguyên có chiều rộng cố định này thực sự đề cập đến loại dữ liệu nào được sử dụng (và chi tiết cụ thể tại sao giá trị này vượt quá mức này) nhưng giá trị giới hạn được thể hiện /usr/include/limits.h
theo kiểu này:
# if __WORDSIZE == 64
# define ULONG_MAX 18446744073709551615UL
# ifdef __USE_ISOC99
# define LLONG_MAX 9223372036854775807LL
# define ULLONG_MAX 18446744073709551615ULL
Và một khi bạn biết điều đó, bạn có thể xác nhận trạng thái thực tế này như vậy:
# getconf -a | grep 'long'
LONG_BIT 64
ULONG_MAX 18446744073709551615
Đây là số nguyên 64 bit và phần này dịch trực tiếp trong hệ vỏ trong bối cảnh đánh giá số học:
# 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
Vì vậy, giữa 2 63 và 2 64 -1, bạn sẽ có được số nguyên âm hiển thị cho bạn cách xa từ ULONG_MAX bạn là 1 . Khi đánh giá đạt đến giới hạn đó và tràn ra, theo bất kỳ thứ tự nào, bạn sẽ không nhận được cảnh báo nào và phần đánh giá đó được đặt lại về 0, điều này có thể mang lại một số hành vi bất thường với ví dụ như lũy thừa liên kết phải :
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
...
Việc sử dụng sh -c 'command'
không thay đổi bất cứ điều gì vì vậy tôi phải cho rằng đây là đầu ra bình thường và tuân thủ. Bây giờ tôi nghĩ rằng tôi đã có một sự hiểu biết cơ bản nhưng cụ thể về phạm vi và giới hạn số học và ý nghĩa của nó trong vỏ để đánh giá biểu thức, tôi nghĩ rằng tôi có thể nhanh chóng xem qua loại dữ liệu nào mà phần mềm khác trong Linux sử dụng. Tôi đã sử dụng một số bash
nguồn mà tôi phải bổ sung cho đầu vào của lệnh này:
{ 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)
Có nhiều đầu ra hơn với các if
câu lệnh và tôi cũng có thể tìm kiếm một lệnh như awk
vậy, v.v ... Tôi nhận thấy biểu thức thông thường mà tôi đã sử dụng không nắm bắt được gì về các công cụ chính xác tùy ý mà tôi có như bc
và dc
.
Câu hỏi
- Lý do nào để không cảnh báo bạn (giống như
awk
khi đánh giá 2 ^ 1024) khi đánh giá số học của bạn tràn ra? Tại sao các số nguyên âm giữa 2 63 và 2 64 -1 tiếp xúc với người dùng cuối khi anh ta đánh giá thứ gì đó? - Tôi đã đọc ở đâu đó rằng một số hương vị của UNIX có thể thay đổi tương tác ULONG_MAX? có ai từng nghe cái này chưa?
- Nếu ai đó tự ý thay đổi giá trị của số nguyên tối đa không dấu trong
limits.h
đó, sau đó biên dịch lạibash
, chúng ta có thể mong đợi điều gì sẽ xảy ra?
Ghi chú
1. Tôi muốn minh họa rõ hơn những gì tôi thấy, vì nó là công cụ thực nghiệm rất đơn giản. Điều tôi nhận thấy là:
- (a) Mọi đánh giá đưa ra <2 ^ 63-1 đều đúng
- (b) Mọi đánh giá đưa ra => 2 ^ 63 lên đến 2 ^ 64 đều cho số nguyên âm:
- Phạm vi của số nguyên đó là x đến y. x = -9223372036854775808 và y = 0.
Xem xét điều này, một đánh giá giống như (b) có thể được biểu thị bằng 2 ^ 63-1 cộng với một cái gì đó trong x..y. Chẳng hạn, nếu chúng tôi được yêu cầu đánh giá theo nghĩa đen (2 ^ 63-1) +100 002 (nhưng có thể là bất kỳ số nào nhỏ hơn trong (a)), chúng tôi nhận được -9223372036854675807. Tôi chỉ nói rõ ràng tôi đoán nhưng điều này cũng có nghĩa là hai biểu thức sau:
- (2 ^ 63-1) + 100 002 VÀ;
- (2 ^ 63-1) + (LLONG_MAX - {những gì vỏ mang lại cho chúng tôi ((2 ^ 63-1) + 100 002), đó là -9223372036854675807}), sử dụng các giá trị dương mà chúng tôi có;
- (2 ^ 63-1) + (9223372036854775807 - 9223372036854675807 = 100 000)
- = 9223372036854775807 + 100 000
thực sự rất gần Biểu thức thứ hai là "2" ngoài (2 ^ 63-1) + 100 002 tức là những gì chúng ta đang đánh giá. Ý tôi là bạn nhận được số nguyên âm cho bạn thấy bạn cách xa 2 ^ 64. Ý tôi là với những số nguyên âm và kiến thức về các giới hạn, bạn cũng không thể hoàn thành việc đánh giá trong phạm vi x..y trong bash shell nhưng bạn có thể ở nơi khác - dữ liệu có thể sử dụng tới 2 ^ 64 theo nghĩa đó (tôi có thể thêm nó lên giấy hoặc sử dụng nó trong bc). Ngoài ra, hành vi tương tự như hành vi của 6 ^ 6 ^ 6 khi đạt đến giới hạn như được mô tả dưới đây trong Q ...
bc
, ví dụ : $num=$(echo 6^6^6 | bc)
. Thật không may, bc
đặt trong các ngắt dòng, vì vậy bạn phải num=$(echo $num | sed 's/\\\s//g')
sau đó; nếu bạn làm điều đó trong một đường ống, có những nhân vật dòng mới thực sự, rất khó xử với sed, mặc dù num=$(echo 6^6^3 | bc | perl -pne 's/\\\s//g')
hoạt động. Trong cả hai trường hợp, bây giờ bạn có một số nguyên có thể được sử dụng, ví dụ : num2=$(echo "$num * 2" | bc)
.
bc
bằng cách cài đặt BC_LINE_LENGTH=0
.