Вычитание между подписанным и неподписанным, за которым следует разделение
Следующие результаты меня смущают:
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
.
Ответы
Ответ 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