Яке обґрунтування, що оболонка баша не застерігає вас від арифметичного переповнення тощо?


9

Існують обмеження, встановлені для можливостей оцінки арифметичної bashоболонки. Посібник є стислим щодо цього аспекту арифметики оболонки, але зазначено :

Оцінювання проводиться в цілих цілих числах без перевірки на переповнення, хоча ділення на 0 захоплюється і позначається як помилка. Оператори та їх пріоритет, асоціативність та значення такі самі, як у мові С.

На яке ціле число фіксованої ширини це стосується насправді, про те, який тип даних використовується (а специфіка, чому це не виходить), але граничне значення виражається /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і т. Д. Я помічаю, що використовуваний регулярний вираз не вловлює нічого про довільні інструменти точності, які я маю, як bcі dc.


Запитання

  1. Яке обґрунтування не попереджає вас (як awkце робиться при оцінці 2 ^ 1024), коли ваша арифметична оцінка переповнена? Чому негативні цілі числа між 2 63 та 2 64 -1 піддаються кінцевому користувачеві, коли він щось оцінює?
  2. Я десь читав, що якийсь аромат UNIX може інтерактивно змінювати ULONG_MAX? Хтось чув про це?
  3. Якщо хтось довільно змінить значення непідписаного цілого максимуму в limits.h, тоді перекомпілює bash, що ми можемо очікувати, що станеться?

Примітка

1. Я хотів більш наочно проілюструвати те, що бачив, оскільки це дуже прості емпіричні речі. Що я помітив, це те, що:

  • (а) Будь-яка оцінка, яка дає <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 - 9223372036854675807 = 100 000)
    • = 9223372036854775807 + 100 000

дійсно дуже близькі. Другий вираз - "2", окрім (2 ^ 63-1) + 100 002, тобто того, що ми оцінюємо. Це те, що я маю на увазі під отриманням від’ємних цілих чисел, що показують, наскільки ви віддалені від 2 ^ 64. Я маю на увазі з тими негативними цілими числами та знанням меж, добре, ви не можете закінчити оцінку в межах діапазону x..y в оболонці bash, але ви можете в іншому місці - дані можна використовувати до 2 ^ 64 в цьому сенсі (я можу додати вгору на папері або використовувати його в bc). Крім того, поведінка схожа на поведінку 6 ^ 6 ^ 6, оскільки межа досягається, як описано нижче в Q ...


5
Я здогадуюсь, що обґрунтування зводиться до "оболонки не є правильним інструментом математики". Це не розроблено для цього і не намагається вирішувати це витончено, як ви показуєте. Чорт, більшість снарядів навіть не справляється з поплавками!
terdon

@terdon Хоча спосіб роботи оболонки з номерами в цьому випадку точно такий же, як і кожна мова високого рівня, яку я коли-небудь чув. Цілі типи мають фіксований розмір і можуть переповнюватись.
goldilocks

@terdon Дійсно, коли я досліджував це з моменту QI 6 ^ 6 ^ 6, я зрозумів це. Я також здогадувався, що причина, чому я не могла знайти багато контенту, це тому, що це стосується C, а то й С99. Оскільки я не є розробником, ані ІТ-людиною, я мушу змиритися з усіма знаннями, що підкріплюють ці припущення. Напевно, хтось, хто вимагає довільної точності, знає про тип даних, але, очевидно, я не та людина :) (але я помітив поведінку awk @ 2 ^ 53 + 1, тобто плаваюче подвійне; просто точність і внутрішня по відношенню до друку і т.д. поза мною !).

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.

Якщо хтось довільно змінить значення непідписаного цілого максимуму у limit.h, тоді перекомпілює bash, що ми можемо очікувати, що станеться?

Це не має значення для того, як виходить арифметика, оскільки це не довільне значення, яке використовується для налаштування системи - це значення зручності, яке зберігає незмінна константа, що відображає обладнання. За аналогією, ви могли б перевизначити c до 55 миль / год, але швидкість світла все одно буде 186 000 миль в секунду. c - це не число, яке використовується для налаштування Всесвіту - це відрахування про природу Всесвіту.

ULONG_MAX точно такий же. Він виводиться / обчислюється виходячи з характеру N-бітових чисел. Змінити його limits.hбуло б дуже погано, якщо ця константа використовується десь, припускаючи, що вона репрезентує реальність системи .

І ви не можете змінити реальність, накладену вашим обладнанням.


1. Я не думаю, що це (засоби цілого представлення) насправді гарантується bash, оскільки це залежить від основної бібліотеки С, а стандарт C не гарантує цього. Однак саме це використовується на більшості звичайних сучасних комп’ютерів.


Я дуже вдячний! Погодячись зі слоном у кімнаті та задумавшись. Так, у першій частині йдеться переважно про слова. Я оновив свій Q, щоб показати, що я мав на увазі. Я досліджу, чому доповнення двох описує деякі з побачених, і ваша відповідь є неоціненною для розуміння цього! Що стосується UNIX Q стосується мене повинно бути що - то неправильно про ARG_MAX з AIX тут . Ура!

1
Насправді ви можете використовувати доповнення двох, щоб визначити значення, якщо ви впевнені, що ви знаходитесь в діапазоні> 2 * $max, як ви описуєте. Мої моменти: 1) це не є метою; 2) переконайтеся, що ви розумієте, чи хочете ви це зробити; 3) це не дуже корисно через дуже обмежене застосування, 4) відповідно до виноски, це насправді не гарантується, що система робить використовувати два доповнення. Коротше кажучи, намагання використати це в програмному коді вважатиметься дуже поганою практикою. Є "велика кількість" бібліотек / модулів (для оболонок під POSIX, bc) - використовуйте ті, якщо вам потрібно.
золотинок

Лише нещодавно я спостерігав щось, що використовувало доповнення двох для впровадження ALU з 4-бітним бінарним суматором зі швидким переносом IC; навіть було порівняння з доповненням (щоб побачити, як це було). Ваше пояснення допомогло мені назвати та зв’язати те, що я бачив тут, із тим, що обговорювалось у цих відео , збільшуючи шанс я реально зрозуміти всі наслідки вниз по лінії, як тільки все зануриться. Дякую знову за це! Ура!
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.