Суммируется ли отрицательное целое число с большим целым числом без знака, повышенным до целого числа без знака? - PullRequest
0 голосов
/ 15 января 2019

После того, как мне посоветовали прочитать "C ++ Primer 5 ed" Стэнли Б. Липмана ", я не понимаю этого:

Страница 66. «Выражения, включающие неподписанные типы»

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // prints -84
std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264

Он сказал:

Во втором выражении значение int -42 преобразуется в unsigned до того, как добавление выполнено. Преобразование отрицательного числа в неподписанное ведет себя точно так же, как если бы мы пытались присвоить это отрицательное значение неподписанному объекту. Значение «оборачивается», как описано выше.

Но если я сделаю что-то вроде этого:

unsigned u = 42;
int i = -10;
std::cout << u + i << std::endl; // Why the result is 32?

Как видите, -10 не преобразуется в unsigned int. Означает ли это, что сравнение происходит до повышения signed integer до unsigned integer?

Ответы [ 5 ]

0 голосов
/ 15 января 2019

Это часть того, что замечательно в представлении дополнения 2. Процессор не знает и не заботится о том, что число подписано или не подписано, операции одинаковы. В обоих случаях расчет верен. Только то, как двоичное число интерпретируется после факта, при печати, имеет значение (могут быть и другие случаи, например, с операторами сравнения)

-10 in 32BIT binary is FFFFFFF6
42 IN 32bit BINARY is  0000002A

Сложив их вместе, для процессора не имеет значения, если они подписаны или не подписаны, результат: 100000020. В 32-битном случае 1 в начале будет помещен в регистр переполнения, а в C ++ просто исчезает , В результате вы получите 0x20, то есть 32.

В первом случае, это в основном то же самое:

-42 in 32BIT binary is FFFFFFD6
10 IN 32bit binary is 0000000A

Сложите их вместе и получите FFFFFFE0

FFFFFFE0 в виде целого числа со знаком - -32 (десятичное число). Расчет верен! Но, поскольку он печатается как неподписанный, он отображается как 4294967264. Речь идет о интерпретации результата.

0 голосов
/ 15 января 2019

"Во втором выражении значение int -42 преобразуется в unsigned до того, как добавление выполнено"

да, это правда

unsigned u = 42;
int i = -10;
std::cout << u + i << std::endl; // Why the result is 32?

Предположим, что мы находимся в 32 битах (это ничего не меняет в 64b, это просто для объяснения), это вычисляется как 42u + ((unsigned) -10), так что 42u + 4294967286u, а результат - 4294967328u, усеченный в 32 бита, поэтому 32. Все было сделано в неподписанном

0 голосов
/ 15 января 2019

-10 преобразуется в целое число без знака с очень большим значением. Причина, по которой вы получаете небольшое число, заключается в том, что дополнение оборачивает вас обратно. С 32-разрядными целыми числами без знака -10 совпадает с 4294967286. Когда вы добавляете 42 к этому, вы получаете 4294967328, но максимальное значение 4294967296, поэтому мы должны взять 4294967328 по модулю 4294967296 и мы получим 32.

0 голосов
/ 15 января 2019

i фактически повышен до unsigned int.

Целые числа без знака в C и C ++ реализуют арифметику в ℤ / 2 n ℤ, где n - количество битов в целочисленном типе без знака. Таким образом, мы получаем

[42] + [-10] ≡ [42] + [2 n - 10] ≡ [2 n + 32] ≡ [32],

с [x] обозначает класс эквивалентности x в ℤ / 2 n ℤ.

Конечно, промежуточный этап выбора только неотрицательных представителей каждого класса эквивалентности, хотя он и происходит формально, не является необходимым для объяснения результата; немедленный

[42] + [-10] ≡ [32]

также будет правильным.

0 голосов
/ 15 января 2019

Ну, я думаю, это исключение из "двух ошибок, которые не делают права":)

Что происходит, так это то, что на самом деле под капотом есть два обтекания (без знака переполнения), и конечный результат оказывается математически правильным.

  • Во-первых, i преобразуется в без знака, и в соответствии с поведением перехода значение std::numeric_limits<unsigned>::max() - 9.

  • Когда это значение суммируется с u, математический результат будет std::numeric_limits<unsigned>::max() - 9 + 42 == std::numeric_limits<unsigned>::max() + 33, что является переполнением, и мы получим еще один результат. Итак, окончательный результат - 32.


Как общее правило в арифметическом выражении, если у вас есть только переполнения без знака (независимо от того, сколько) и если конечный математический результат представлен в типе данных выражения, то значение выражения будет математически корректным. Это является следствием того факта, что целые числа без знака в C ++ подчиняются законам арифметики по модулю 2 n (см. Ниже).


Важное замечание. Согласно C ++ арифметика без знака не переполняется:

§6.9.1 Фундаментальные типы [basic.fundamental]

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

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

Однако я оставлю в своем ответе «переполнение», чтобы выразить значения, которые нельзя представить в обычной арифметике.

Также то, что мы в разговорной речи называем «обтекание», на самом деле является просто арифметической природой по модулю целых чисел без знака. Я, однако, буду использовать «обтекание» еще и потому, что его легче понять.

...