Подтвердить что ты не робот

Вычитание между подписанным и неподписанным, за которым следует разделение

Следующие результаты меня смущают:

int i1 = 20-80u;    // -60
int i2 = 20-80;     // -60
int i3 =(20-80u)/2; // 2147483618
int i4 =(20-80)/2;  // -30
int i5 =i1/2;       // -30
  • i3, по-видимому, вычисляется как (20u-80u)/2 вместо (20-80u)/2
  • предположительно i3 совпадает с i5.
4b9b3361

Ответ 1

IIRC, арифметическая операция между подписанным и unsigned int будет приводить к неподписанному результату.

Таким образом, 20 - 80u выдает результат без знака, эквивалентный -60: if unsigned int - это 32-разрядный тип, результатом которого является 4294967236.

Кстати, назначение i1 приводит к результату, определяемому реализацией, потому что число слишком велико, чтобы соответствовать. Получение -60 является типичным, но не гарантированным.

Ответ 2

int i1 = 20-80u;    // -60

Операнды разные, поэтому требуется преобразование. Оба операнда преобразуются в общий тип (в этом случае unsigned int). Результат, который будет большим значением unsigned int (60 меньше, чем UINT_MAX + 1, если мои вычисления верны), будет преобразован в int, прежде чем он будет сохранен в i1. Поскольку это значение выходит за пределы диапазона int, результат будет определяться реализацией, может быть ловушечным представлением и, таким образом, может привести к поведению undefined при попытке его использования. Однако в вашем случае он по совпадению преобразуется в -60.


int i3 =(20-80u)/2; // 2147483618

Продолжая работу с первого примера, я предполагал, что результат 20-80u будет на 60 меньше, чем UINT_MAX + 1. Если UINT_MAX равно 4294967295 (общее значение для UINT_MAX), это означает, что 20-80u есть 4294967236... и 4294967236 / 2 равно 2147483618.


Что касается i2 и других, сюрпризов не должно быть. Они следуют обычным математическим вычислениям без конверсий, усечений или переполнений.

Ответ 3

Двоичные арифметические операторы будут выполнять обычные арифметические преобразования в своих операндах, чтобы привести их к общему типу.

В случае i1, i3 и i5 общий тип будет беззнаковым int, поэтому результат будет также беззнаковым int. Беззнаковые числа будут перенесены по модулю арифметики, и поэтому вычитание немного большего значения без знака приведет к тому, что число будет близко к unsigned int max, которое не может быть представлено int.

Итак, в случае i1 мы получаем преобразование, определенное реализацией, поскольку значение не может быть представлено. В случае i3 деление на 2 возвращает значение без знака обратно в диапазон int, и поэтому мы получим большое значение знака после преобразования.

Соответствующие разделы, составляющие проект стандарта С++, заключаются в следующем. Раздел 5.7 [expr.add]:

Аддитивные операторы + и - группа слева направо. Обычные арифметические преобразования выполняются для операнды арифметики или типа перечисления.

Обычные арифметические преобразования рассматриваются в разделе 5, и он говорит:

Многие двоичные операторы, ожидающие операндов арифметики или типа перечисления, вызывают конверсии и доходность аналогичным образом. Цель состоит в том, чтобы дать общий тип, который также является типом результата. Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:

[...]

  • В противном случае, если операнд с целым типом без знака имеет ранг, больший или равный ранга типа другого операнда, операнд со знаком целочисленного типа должен быть преобразован в тип операнда с целым типом без знака.

и для преобразования из значения, которое не может быть представлено для подписанного типа, раздел 4.7 [conv.integral]:

Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и ширина битового поля); в противном случае значение определяется реализацией.

и для целых без знака подчиняется по модулю арифметического раздела 3.9.1 [basic.fundamental]:

Неподписанные целые числа должны подчиняться законам арифметики по модулю 2n, где n - количество бит в значении представление этого конкретного размера целого .48