Любопытная арифметическая ошибка - 255x256x256x256 = 18446744073692774400
У меня возникла странная вещь, когда я программировал под С++. Это о простом умножении.
код:
unsigned __int64 a1 = 255*256*256*256;
unsigned __int64 a2= 255 << 24; // same as the above
cerr()<<"a1 is:"<<a1;
cerr()<<"a2 is:"<<a2;
Интересно, что результат:
a1 is: 18446744073692774400
a2 is: 18446744073692774400
тогда как это должно быть: (используя калькулятор подтверждает)
4278190080
Может ли кто-нибудь сказать мне, как это возможно?
Ответы
Ответ 1
255*256*256*256
все операнды int
вы переполнены int
. Переполнение знакового целого является undefined поведением в C и С++.
EDIT:
обратите внимание, что выражение 255 << 24
во втором объявлении также вызывает поведение undefined, если ваш int
тип 32-bit
. 255 x (2^24)
4278190080
, который не может быть представлен в 32-bit
int
(максимальное значение обычно 2147483647
на 32-bit
int
в двух дополнительных представлениях).
C и С++ говорят для E1 << E2
, что если E1
имеет тип со знаком и положителен и что E1 x (2^E2)
не может быть представлен в типе E1
, программа вызывает поведение undefined. Здесь ^
- математический оператор мощности.
Ответ 2
Ваши литералы int
. Это означает, что все операции фактически выполняются на int
и быстро переполняются. Это переполненное значение при преобразовании в 64-битный int без знака - это значение, которое вы наблюдаете.
Ответ 3
Возможно, стоит объяснить, что получилось с номером 18446744073692774400. С технической точки зрения, написанные вами выражения запускают "поведение undefined", и поэтому компилятор мог бы создать что-либо в качестве результата; однако, предполагая, что int
- это 32-битный тип, который он почти всегда находится в настоящее время, вы получите тот же "неправильный" ответ, если напишете
uint64_t x = (int) (255u*256u*256u*256u);
и это выражение не вызывает поведение undefined. (Преобразование из unsigned int
в int
связано с реализацией, определяемой реализацией, но поскольку за многие годы никто не создал процессор с дополнениями или знаками и значениями, все реализации, с которыми вы, вероятно, столкнетесь, определите его точно так же.) Я написал актерский состав в стиле C, потому что все, что я говорю здесь, в равной степени относится к C и С++.
Прежде всего, давайте посмотрим на умножение. Я пишу правую сторону в шестнадцатеричной форме, потому что легче понять, что происходит на этом пути.
255u * 256u = 0x0000FF00u
255u * 256u * 256u = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)
В последнем результате 0xFF000000u
имеет самый старший бит 32-битного числа. Таким образом, это значение для подписанного 32-битного типа приводит к тому, что он становится отрицательным как-бы если из него вычиталось 2 32 (эта операция, определенная реализацией, упомянутая выше).
(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216
Я пишу шестнадцатеричное число там, sans u
суффикс, чтобы подчеркнуть, что бит-шаблон значения не изменяется при преобразовании его в подписанный тип; он только переинтерпретируется.
Теперь, когда вы назначаете -16777216 переменной uint64_t
, она преобразуется обратно в unsigned as-if, добавляя 2 64. (В отличие от преобразования без знака в подпись эта семантика предписывается стандартом.) Это изменяет битовый шаблон, устанавливая все высокие 32 бита числа в 1 вместо 0, как вы ожидали:
(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
И если вы напишете 0xFFFFFFFFFF000000
в десятичной форме, вы получите 18446744073692774400.
Как заключительный совет, всякий раз, когда вы получаете "невозможное" целое число от C или С++, попробуйте распечатать его в шестнадцатеричном формате; гораздо проще увидеть тактику арифметики с фиксированной шириной в два порядка.
Ответ 4
Ответ прост - переполнен.
Ответ 5
Здесь переполнение произошло в int и когда вы назначаете его неподписанному int64, его преобразованный в 18446744073692774400 вместо 4278190080