Какова причина того, что оболочка bash не предупреждает вас об арифметическом переполнении и т. Д.?

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, является правильной
  • (б) Любая оценка, которая дает => 2 ^ 63 до 2 ^ 64, дает отрицательное целое число:
    • Диапазон этого целого числа от x до y. х = -9223372036854775808 и у = 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 (я мог бы добавить это на бумаге или использовать его в до н.э). Помимо этого, однако, поведение аналогично 6 ^ 6 ^ 6, так как предел достигнут, как описано ниже в Q ...


источник
5
Я предполагаю, что обоснование сводится к тому, что «оболочка не является правильным инструментом для математики». Он не предназначен для этого и не пытается изящно справиться с этим, как вы показываете. Черт, большинство снарядов даже не имеют дело с поплавками!
Terdon
@terdon Хотя способ, которым оболочка работает с числами в этом случае, точно такой же, как и у всех языков высокого уровня, о которых я когда-либо слышал. Целочисленные типы имеют фиксированный размер и могут переполняться.
Златовласка
@terdon Действительно, когда я исследовал это с 6 ^ 6 ^ 6 времени, QI осознал это. Я также догадался, что причина, по которой я не смог найти много контента, заключалась в том, что это было связано с C или даже C99. Поскольку я не являюсь ни разработчиком, ни ИТ-специалистом, я должен смириться со всеми знаниями, которые подтверждают эти предположения. Конечно, кто-то, кто требует произвольной точности, знает о типе данных, но, очевидно, я не тот человек :) (но я заметил, что поведение awk @ 2 ^ 53 + 1, то есть float double; просто точность и внутренняя по сравнению с печатью и т. Д. Мне не под силу !).
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).
Златовласка
1
... Кто-то здесь указал, что вы можете отключить эту функцию разрыва строки bc, установив BC_LINE_LENGTH=0.
Златовласка

Ответы:

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, а стандарт C не гарантирует этого. Тем не менее, это то, что используется на большинстве обычных современных компьютеров.

лютик золотистый
источник
Я очень благодарен! Примириться со слоном в комнате и подумать. Да, в первой части речь идет в основном о словах. Я обновил свой Q, чтобы показать, что я имел в виду. Я исследую, почему в дополнении «два» описывается кое-что из того, что я видел, и ваш ответ неоценим в понимании этого! Что касается UNIX Q, я, должно быть, неправильно понял что-то о ARG_MAX с AIX здесь . Ура!
1
На самом деле вы можете использовать дополнение до двух, чтобы определить значение, если вы уверены, что находитесь в диапазоне> 2 * $max, как вы описываете. Мои соображения: 1) это не цель, 2) убедитесь, что вы понимаете, хотите ли вы это сделать, 3) это не очень полезно из-за очень ограниченной применимости, 4) в соответствии со сноской, фактически не гарантируется, что система использовать два дополнения. Короче говоря, пытаться использовать это в программном коде было бы очень плохой практикой. Существуют библиотеки / модули с «большим числом» (для оболочек под POSIX, bc) - используйте их, если вам нужно.
Златовласка
Только недавно я наблюдал что-то, что использовало дополнение двух для реализации ALU с 4-битным двоичным сумматором с быстрым переносом IC; было даже сравнение со своим дополнением (чтобы увидеть, как это было). Ваше объяснение сыграло важную роль в том, что я смог назвать и связать то, что я увидел здесь, с тем, что обсуждалось в этих видеороликах , что повысило вероятность того, что я действительно смогу осознать все последствия, как только это произойдет. Спасибо за это еще раз! Ура!