Правильно ли C ++ 20 определяет сдвиг влево для целых чисел со знаком, которые «переполняются»? - PullRequest
9 голосов
/ 02 апреля 2019

В текущем стандартном драфте C ++ оператор левого сдвига определяется следующим образом [expr.shift] :

Значение E1 << E2 равно , уникальное значение соответствует E1×2^E2 по модулю 2^N, где N - ширина типа результата.

Рассмотрим int E1 = 2^31-1 = 2'147'483'647, E2 = 1 и int, имеющие 32 бита. Тогда существует бесконечное число чисел, равных E1×2^E2 = 4'294'967'294 по модулю 2^N = 2^32, а именно, все числа 4'294'967'294 + k×2^32, где k - произвольное целое число. Примеры: 4'294'967'294 (k=0) или -2 (k=-1).

Я не понимаю, что Стандарт подразумевает под уникальным значением из этих чисел. Означает ли это уникальное значение, которое может быть представлено результирующим типом данных ? Тогда, я полагаю, результат определяется как -2. Правильно ли это истолковано?

До C ++ 20 определение было другим, и это могло привести к неопределенному поведению. Я полагаю, что изменение связано с обязательным представлением двух чисел с отрицательным знаком.

Фактически, теперь больше не требуется, чтобы E2 был неотрицательным. Поэтому кажется, что -1 << 1 определяется как -2. Это тоже правильно?

Ответы [ 2 ]

5 голосов
/ 02 апреля 2019

Означает ли это уникальное значение, которое может быть представлено результирующим типом данных

Да.Набор чисел, равный E1×2^E2 по модулю 2^N, бесконечен, но в любом интервале размера 2^N имеется только одно значение, поэтому в целочисленном типе ширины N. * 1009 имеется только одно значение.*

Если мы посмотрим в предложении "p0907R1 Подписанные целые числа - дополнение двух" , мы найдем похожую фразу с "уникальным представлением", которая делает это более ясным:

Конверсияот подписи до неподписания всегда четко определено: результатом является уникальное значение типа назначения , соответствующее исходному целому числу по модулю 2 N .

Тогда, я полагаю, результат определен как -2.Верна ли эта интерпретация?

Да

На x64 эквивалентная инструкция asm равна shlx (логический сдвиг влево)

Полагаю, это изменение связано с обязательным представлением двухзначных чисел с отрицательными знаками.

Правильно.Как и в случае с неподписанными типами, теперь также подписанные типы математически представляют классы эквивалентности (ну, мне не ясно, насколько это верно, поскольку похоже, что они все еще хотят сохранить некоторые случаи UB при переполнении).

2 голосов
/ 02 апреля 2019

Итак, мы знаем, что:

E1 = 2147483647
E2 = 1
N = sizeof(int) * CHAR_BIT = 4 * 8 = 32

Давайте вычислим E1×2^E2 modulo 2^N ( по модулю - остаток от деления):

x = E1×2^E2 mod 2^N = 2147483647 * 2 ^ 1 mod 4294967296 = 4294967294 mod 4294967296 = 4294967294

Тогда мы идем к здесь :

Для каждого значения x целого типа со знаком значение соответствующий целочисленный тип без знака, равный x по модулю 2 N, имеет то же значение соответствующих битов в его представлении значения.

и я думаю, что нам также нужно:

Представление base-2 значения целочисленного типа со знаком является Base-2 представление конгруэнтного значения соответствующего целочисленный тип без знака.

Это означает, что x = 4294967294 равно x = -2 для signed int. Таким образом, результат будет -2.

Поэтому кажется, что -1 << 1 определяется как -2. Это тоже правильно? </p>

 (signed)-1 << 1 = 
 4294967295 << 1 = 
 4294967295 * 2 ^ 1 mod 4294967296 = 
 8589934590 mod 4294967296 = 
 4294967294 = 
 (signed)-2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...