mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-17 21:04:35 +03:00
Punctuation: replace --/--- with —.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
|
||||
# Переполнение целых знаковых чисел
|
||||
|
||||
Большая часть написанного и еще не написанного кода любой программы так или иначе работает с числами. Вычисление по каким-либо формулам, увеличение или уменьшение счетчиков итераций циклов, рекурсивных вызовов, элементов контейнеров -- работа с числами везде.
|
||||
Большая часть написанного и еще не написанного кода любой программы так или иначе работает с числами. Вычисление по каким-либо формулам, увеличение или уменьшение счетчиков итераций циклов, рекурсивных вызовов, элементов контейнеров — работа с числами везде.
|
||||
|
||||
Компьютер не может напрямую работать с бесконечно "длинными" числами -- хранить все их цифры. Как бы много оперативной памяти у нас ни было -- все же она конечна. Да и хранить, и обрабатывать величины, сопоставимые с числом атомов в видимой части вселенной -- безнадежное занятие. Так что ограничения типов `int64` или `int128` не очень нас-то и ограничивают
|
||||
Компьютер не может напрямую работать с бесконечно "длинными" числами — хранить все их цифры. Как бы много оперативной памяти у нас ни было — все же она конечна. Да и хранить, и обрабатывать величины, сопоставимые с числом атомов в видимой части вселенной — безнадежное занятие. Так что ограничения типов `int64` или `int128` не очень нас-то и ограничивают
|
||||
|
||||
Тем не менее при выполнении операций над целыми числами мы все же имеем шанс выпасть за пределы допустимого диапазона (например, `[-2^31, 2^31-1]` для `int32`), и тут в игру вступают особенности поддержки целых чисел для того или иного языка программирования, а также, быть может, особенности реализации конкретной платформы.
|
||||
|
||||
@@ -16,12 +16,12 @@ iadd x 5
|
||||
|
||||
В реализации конкретного языка программирования может быть проверка флага переполнения и сообщение об ошибке. А может и не быть. Может быть гарантия "цикличности" значений (после `2^31-1` идет `-2^31`), а может и не быть.
|
||||
|
||||
Проверки и гарантии -- это дополнительные инструкции, которые нужно генерировать компилятору, а процессору потом исполнять.
|
||||
Проверки и гарантии — это дополнительные инструкции, которые нужно генерировать компилятору, а процессору потом исполнять.
|
||||
|
||||
В языке C++ решили не жертвовать производительностью и заставлять компиляторы генерировать код проверки, а объявили переполнение целых знаковых (`signed`) чисел неопределенным, открывая простор для оптимизаций. Компилятор может генерировать любой код, какой ему вздумается, ориентируясь лишь на одно правило: переполнения не бывает.
|
||||
|
||||
|
||||
Многие программисты свято верят, что переполнение чисел работает, как ожидается, "циклично" -- и пишут проверки вида
|
||||
Многие программисты свято верят, что переполнение чисел работает, как ожидается, "циклично" — и пишут проверки вида
|
||||
|
||||
```C++
|
||||
if (x > 0 && a > 0 && x + a <= 0) {
|
||||
@@ -122,7 +122,7 @@ ErrorOrInteger<I> div(I a, std::type_identity_t<I> b) {
|
||||
|
||||
if (a == std::numeric_limits<I>::min && b == -1) {
|
||||
// диапазон [min, max] несимметричный относительно 0.
|
||||
// abs(min) > max -- будет переполнение
|
||||
// abs(min) > max — будет переполнение
|
||||
return ArithmeticError::Overflow;
|
||||
}
|
||||
return a / b;
|
||||
@@ -157,7 +157,7 @@ ErrorOrInteger<I> mod(I a, std::type_identity_t<I> b) {
|
||||
|
||||
Однако, если ваша программа только и делает, что ожидает и выполняет IO операции, то траты в два раза большего числа тактов на сложение или умножение никто и не заметит. Да и язык C++ для таких программ чаще всего не лучший выбор.
|
||||
|
||||
Итак, если вы работаете только лишь с беззнаковыми числами (`unsigned`), то с неопределенным поведением при переполнеии никаких проблем нет -- все определено как вычисления по модулю `2^N` (N -- количество бит для выбранного типа чисел).
|
||||
Итак, если вы работаете только лишь с беззнаковыми числами (`unsigned`), то с неопределенным поведением при переполнеии никаких проблем нет — все определено как вычисления по модулю `2^N` (N — количество бит для выбранного типа чисел).
|
||||
|
||||
Если же вы работаете со знаковыми числами, либо используйте безопасные обертки, сообщающие каким-либо образом об ошибках. Либо выводите ограничения на входные данные програмы целиком таким образом, чтобы переполнения не возникало, и не забывайте эти ограничения проверять. Все просто, да?
|
||||
|
||||
@@ -165,7 +165,7 @@ ErrorOrInteger<I> mod(I a, std::type_identity_t<I> b) {
|
||||
А также тестовые `constexpr` вычисления.
|
||||
|
||||
|
||||
Также проблемы неопределенного поведения при переполнении касаются битовых сдвигов влево для отрицательных чисел (или при сдвиге положительного числа с залезанием в знаковый бит). Начиная с C++20, стандарт требует фиксированной единой реализации отрицательных чисел -- через дополнительный код ([_two's complement_](https://en.wikipedia.org/wiki/Two%27s_complement)), и многие проблемы сдвигов сняты. Тем не менее все равно стоит следовать общей рекомендации: любые битовые операции выполнять только в `unsigned` типах.
|
||||
Также проблемы неопределенного поведения при переполнении касаются битовых сдвигов влево для отрицательных чисел (или при сдвиге положительного числа с залезанием в знаковый бит). Начиная с C++20, стандарт требует фиксированной единой реализации отрицательных чисел — через дополнительный код ([_two's complement_](https://en.wikipedia.org/wiki/Two%27s_complement)), и многие проблемы сдвигов сняты. Тем не менее все равно стоит следовать общей рекомендации: любые битовые операции выполнять только в `unsigned` типах.
|
||||
|
||||
|
||||
Стоит заметить, что сужающее преобразование из целочисленного типа в другой целочисленный тип к неопределенному поведению не приводит, и выполнять побитовое `и` с маской перед присваиванием переменной меньшего типа не обязательно. Но желательно, чтобы избежать предупреждений компилятора
|
||||
@@ -192,7 +192,7 @@ static_assert(IntegerPromotionUB(65535) == 1); // won't compile
|
||||
|
||||
```C++
|
||||
x *= x; // переписывается как x = x * x;
|
||||
// тип uint16 меньше чем тип int -- для * выполняется неявное приведение к int.
|
||||
// тип uint16 меньше чем тип int — для * выполняется неявное приведение к int.
|
||||
```
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user