bitlvl
RU

Калькулятор переполнения и целочисленной арифметики

Введите выражение, выберите тип и язык и посмотрите, что именно делает машина: какие операнды продвигаются или преобразуются, где результат перестаёт помещаться, что это — wraparound, panic или неопределённое поведение, и биты до и после.

Результаты и сообщения этого инструмента выводятся на английском языке.

Почему целые числа удивляют

У машинного целого фиксированное число битов, поэтому любая операция может дать значение, которое не помещается. Что происходит дальше, решает не аппаратура (она просто заворачивает значение по кругу), а язык:

ЯзыкЗнаковое переполнениеБеззнаковое переполнениеСдвиг на ≥ разрядностиДеление на ноль
C / C++неопределённое поведение: оптимизатор считает, что этого никогда не происходитзаворачивается по модулю 2ⁿ (определено)не определеноне определено (SIGFPE на x86)
Rustpanic в debug, заворачивание в release; checked_/wrapping_/saturating_/overflowing_ задаются явнокак у знаковогоpanic в debugpanic
Goзаворачивается (определено)заворачивается0 / −1, без panicpanic
Javaзаворачивается (определено); Math.addExact бросает исключениебеззнаковых типов нет (char — 16-битный беззнаковый)величина сдвига маскируется до 5/6 битArithmeticException
JavaScriptчисла — это double: точны до 2⁵³, дальше округление; побитовые операторы работают с int32величина сдвига маскируется до 5 битInfinity / NaN

Правила C, на которых спотыкаются

  • Продвижение целых типов. Всё, что меньше int, перед арифметикой становится int, поэтому uint8_t a = 200, b = 100; a + b равно 300, и только присваивание обратно в uint8_t усекает до 44. ~x для uint8_t даёт отрицательный int.
  • Обычные арифметические преобразования. Когда знаковый тип встречается с беззнаковым той же разрядности, знаковая сторона становится беззнаковой: -1 > 0u истинно, if (len - 1 < 0) никогда не истинно, если len имеет тип size_t.
  • Знаковое переполнение — это UB. if (x + 1 < x) в качестве проверки переполнения удаляется GCC и Clang при включённой оптимизации, потому что они вправе считать, что x + 1 никогда не переполняется. Проверяйте до операции (x > INT_MAX - y) или используйте __builtin_add_overflow.
  • Сдвиги. Сдвиг на разрядность или больше, а также сдвиг влево отрицательного значения не определены; x86 маскирует величину сдвига, ARM даёт ноль, компилятор делает то, что предписывает его модель.
  • Деление. Усекает к нулю, поэтому -7 / 2 == -3 и -7 % 2 == -1; INT_MIN / -1 переполняется и на x86 действительно вызывает trap.

Как читать вывод

Результат показан в том типе, в котором он оказывается, с его битовым шаблоном и теми же битами, прочитанными с противоположной знаковостью, — именно это вы видите, когда значение пересекает границу (int) / (unsigned) или сохраняется и читается через другой тип. Список шагов — это порядок вычисления с типом на каждом шаге; примечания — это места, где язык сделал что-то помимо обычной арифметики.

Вопросы

Почему результат для C отличается от того, что напечатала моя программа?

Для неопределённого поведения нет единственно правильного ответа: эта страница показывает значение с заворачиванием, которое даёт неоптимизированный код, и помечает шаг как UB. С -O2 компилятор мог предположить, что переполнение невозможно, и сгенерировать другой код. Скомпилируйте с -fsanitize=undefined, чтобы увидеть точное место.

Что делает выбор типа?

Это тип литералов без суффикса или приведения, который в C по умолчанию int. Выберите u8 и введите 200 + 100, чтобы увидеть panic в Rust или продвижение до int и успешный результат в C.

Как записать приведение типа?

В стиле C: (u8) expr, (uint32_t) expr, (int) expr. В стиле Rust: expr as u8. Оба варианта работают в любом языковом режиме. INT_MAX, INT_MIN, UINT_MAX, LONG_MAX, SIZE_MAX доступны как имена.

Поддерживаются ли 128 бит?

Да: i128 и u128 вычисляются точно (вся арифметика выполняется с произвольной точностью, а затем результат подгоняется под тип).

А что с плавающей точкой?

Не здесь; этим занимается визуализатор float IEEE 754 на этом сайте. Этот инструмент только о том, что происходит с целыми числами в типах фиксированной разрядности.