Беззнаковая арифметика и целочисленное переполнение

Я пытаюсь понять арифметическое переполнение. Предположим, у меня есть следующее,

unsigned long long x;
unsigned int y, z;

x = y*z;

y*z может привести к целочисленному переполнению. Устраняет ли приведение одного из операндов к unsigned long long эту проблему. Каков ожидаемый результат умножения 64-битного операнда на 32-битный операнд?


person ssn    schedule 10.03.2014    source источник
comment
Оба операнда сначала повышаются до более широкого типа, если они не равного типа.   -  person Kerrek SB    schedule 11.03.2014
comment
Из многих примеров я могу сказать, что для результата используется более крупный шрифт. Так что да, приведение одного из операндов решит проблему.   -  person Vladp    schedule 11.03.2014
comment
@KerrekSB: в данном случае это не применимо. y и z оба являются unsigned int.   -  person Keith Thompson    schedule 11.03.2014
comment
@KeithThompson: Нет (кажется, я так сказал?), это был просто ответ на вопрос...   -  person Kerrek SB    schedule 11.03.2014


Ответы (3)


unsigned long long x;
unsigned int y, z;

x = y*z;

На оценку выражения y*z не влияет контекст, в котором оно появляется. Он умножает два значения unsigned int, давая результат unsigned int. Если математический результат не может быть представлен в виде значения unsigned int, результат будет зациклен. Затем присваивание неявно преобразует (возможно, усеченный) результат из unsigned int в unsigned long long.

Если вы хотите, чтобы умножение давало результат unsigned long long, вам нужно явно преобразовать один или оба операнда:

x = (unsigned long long)y * z;

или, чтобы быть более явным:

x = (unsigned long long)y * (unsigned long long)z;

Оператор умножения * языка C применяется только к двум операндам одного типа. Из-за этого, когда вы даете ему операнды разных типов, они преобразуются в какой-то общий тип до того, как будет выполнено умножение. Правила могут быть немного сложными, когда вы смешиваете типы со знаком и без знака, но в этом случае, если вы умножаете unsigned long long на unsigned int, операнд unsigned int повышается до unsigned long long.

Если unsigned long long по крайней мере в два раза шире unsigned int, как в большинстве систем, то результат не будет переполняться или циклически повторяться, потому что, например, 64-битный unsigned long long может содержать результат умножение любых двух 32-битных значений unsigned int. Но если вы работаете в системе, где, например, int и long long имеют разрядность 64 бита, вы все равно можете использовать обход overflow, что даст вам результат в x, который не равен математическому произведению y и z.

person Keith Thompson    schedule 10.03.2014

Вы явно предполагаете, что unsigned int 32-битный, а unsigned long long 64-битный. Они не должны быть, допустим, это.

64-битный операнд, полученный путем преобразования 32-битного операнда, по-прежнему умещается в 32 бита. Таким образом, в y*(unsigned long long)z, где каждый из операндов сначала повышается до unsigned long long, результат вычисляется как unsigned long long и не может «переполняться», потому что это умножение двух квантификаторов, каждый из которых занимает 32 бита.

(Кроме того, в словаре стандарта C беззнаковые операции не «переполняются». Переполнение — это неопределенное поведение, при котором результат выходит за пределы целевого типа. Что делают беззнаковые операции, так это «обтекание»).

person Pascal Cuoq    schedule 10.03.2014

Если один операнд шире другого, компилятор должен (или вести себя так, как если бы он был) преобразовывать оба операнда в один и тот же размер, поэтому приведение одного к большему размеру приведет к правильному поведению.

Это указано в стандартах C и C++. В стандарте С++ 11 (черновик n3337) говорится в пятой главе, оператор 9:

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

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

person Mats Petersson    schedule 10.03.2014
comment
Однако это не вина компилятора. Язык требует такого поведения. - person Kerrek SB; 11.03.2014
comment
Да, формулировка изменена, чтобы отразить это и цитату из предоставленного стандартного документа. - person Mats Petersson; 11.03.2014
comment
Красивый. Когда у нас есть формулировка из стандарта, нам почти никогда не нужно говорить о компиляторах вообще :-) (Тема и так слишком сложна, если не вводить всю периферию!) - person Kerrek SB; 11.03.2014
comment
Как это применимо в данном случае? И y, и z относятся к типу unsigned int. - person Keith Thompson; 11.03.2014
comment
@KeithThompson: ОП спрашивает, помогает ли приведение одного значения к unsigned long long. - person Kerrek SB; 11.03.2014
comment
@KerrekSB: Вы правы, я недостаточно внимательно прочитал вопрос. - person Keith Thompson; 11.03.2014
comment
@KeithThompson: Не беспокойтесь, в нем отсутствуют 50% вопросительных знаков, которые ожидает англоговорящий, что я считаю особенно дурным тоном. - person Kerrek SB; 11.03.2014
comment
То есть компилятор не участвует? Стандарт делает это просто существуя? - person Mats Petersson; 11.03.2014