Следующие результаты приводят меня в замешательство:
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
,IIRC, арифметическая операция между подписанным и неподписанным int приведет к неподписанному результату.
Таким образом, 20 - 80u
производит неподписанный результат, эквивалентный -60
: если unsigned int
32-битный тип, результат 4294967236.
Кстати, присвоение этого i1
производит от реализации результат, потому что число слишком велико, чтобы соответствовать. Получение -60
типично, но не гарантировано.
int i1 = 20-80u; // -60
Это тонкие демоны! Операнды разные, поэтому необходимо преобразование. Оба операнда преобразуются в общий тип ( unsigned int
, в этом случае). Результат, который будет большим unsigned int
значение (на 60 меньше, чем UINT_MAX + 1
если мои расчеты верны) будут преобразованы в int
прежде чем он хранится в i1
, Поскольку это значение выходит за пределы диапазона int
результат будет определен реализацией, может быть представлением ловушки и, следовательно, может вызвать неопределенное поведение при попытке его использования. Тем не менее, в вашем случае это по совпадению конвертируется в -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
и другие, не должно быть никаких сюрпризов. Они следуют обычным математическим вычислениям без каких-либо преобразований, усечений, переполнений или другого поведения, определенного реализацией.
Бинарные арифметические операторы будут выполнять обычные арифметические преобразования на их операнды, чтобы привести их к общему типу.
В случае i1
, i3
а также i5
общий тип будет без знака int и поэтому результат также будет без знака int. Числа без знака будут переноситься с помощью арифметики по модулю, поэтому вычитание немного большего значения без знака приведет к числу, близкому к целому числу без знака, которое не может быть представлено целым числом.
Так что в случае i1
мы получаем преобразование, определяемое реализацией, поскольку значение не может быть представлено. В случае i3
деление на 2
возвращает значение без знака обратно в диапазон int, и в результате мы получаем большое значение int со знаком после преобразования.
Соответствующие разделы из проекта стандарта C ++ следующие. Раздел 5.7
[Expr.add]:
Аддитивные операторы + и — группа слева направо. Обычные арифметические преобразования выполняются для
операнды арифметического или перечислимого типа.
Обычные арифметические преобразования рассматриваются в разделе 5
и это говорит:
Многие бинарные операторы, которые ожидают операнды арифметического или перечислимого типа, вызывают преобразования и дают
[…]
Типы результатов аналогичным образом. Цель состоит в том, чтобы получить общий тип, который также является типом результата.
Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:
- В противном случае, если операнд с целым типом без знака имеет ранг больше или равен
ранг типа другого операнда, операнд со знаком целого типа должен быть преобразован в
тип операнда с целым типом без знака.
и для преобразования из значения, которое не может быть представлено для подписанного типа, раздел 4.7
[Conv.integral]:
Если тип назначения подписан, значение не изменяется, если оно может быть представлено в типе назначения (и
ширина битового поля); в противном случае значение определяется реализацией.
и для целых чисел без знака подчиняется модулю арифметического раздела 3.9.1
[Basic.fundamental]:
Целые числа без знака должны подчиняться законам арифметики по модулю 2n, где n — количество битов в значении
представление этого конкретного размера целого числа.