Я столкнулся со странной вещью, когда программировал под с ++. Это о простом умножении.
Код:
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
Кто-нибудь может сказать мне, как это возможно?
255*256*256*256
все операнды int
вы переполнены int
, Переполнение целого числа со знаком — неопределенное поведение в C и C ++.
РЕДАКТИРОВАТЬ:
обратите внимание, что выражение 255 << 24
в вашем втором объявлении также вызывает неопределенное поведение, если ваш int
тип 32-bit
, 255 x (2^24)
является 4278190080
который не может быть представлен в 32-bit
int
(максимальное значение обычно 2147483647
на 32-bit
int
в двух дополнительных представлениях).
C и C ++ оба говорят за E1 << E2
что если E1
имеет подписанный тип и положительный и что E1 x (2^E2)
не может быть представлен в виде E1
программа вызывает неопределенное поведение. Вот ^
это математическое мощность оператор.
Ваши литералы int
, Это означает, что все операции фактически выполняются на int
и быстро переполнить. Это переполненное значение при преобразовании в 64-разрядное целое число без знака является значением, которое вы наблюдаете.
Возможно, стоит объяснить, что случилось с производством числа 18446744073692774400. Технически говоря, написанные вами выражения вызывают «неопределенное поведение», и поэтому компилятор мог сгенерировать что-нибудь в результате; однако, предполагая, int
это 32-битный тип, которым он почти всегда является сейчас, вы получите тот же «неправильный» ответ, если напишите
uint64_t x = (int) (255u*256u*256u*256u);
и это выражение делает не вызвать неопределенное поведение. (Преобразование из unsigned int
в int
включает в себя от реализации поведение, но так как никто не создавал CPU с единичным дополнением или величиной знака и величины в течение многих лет, все реализации, с которыми вы, вероятно, столкнетесь, определяют его точно так же.) Я написал приведение в стиле C, потому что все, что я представляю сказанное здесь в равной степени относится к C и C ++.
Прежде всего, давайте посмотрим на умножение. Я пишу правую часть в шестнадцатеричном виде, потому что так легче увидеть, что происходит.
255u * 256u = 0x0000FF00u
255u * 256u * 256u = 0x00FF0000u
255u * 256u * 256u * 256u = 0xFF000000u (= 4278190080)
Этот последний результат, 0xFF000000u
, имеет старший бит из установленного 32-битного числа. Приведение этого значения к подписанный 32-битный тип, следовательно, становится отрицательным, как будто 232 был вычтен из него (это операция, определяемая реализацией, о которой я упоминал выше).
(int) (255u*256u*256u*256u) = 0xFF000000 = -16777216
Я пишу шестнадцатеричное число там, без u
суффикс, чтобы подчеркнуть, что битовый паттерн значение не изменяется при преобразовании его в тип со знаком; это только переосмыслено.
Теперь, когда вы назначаете -16777216 для uint64_t
переменная, она обратно преобразуется в без знака как-будто, добавив 264. (В отличие от преобразования без знака в подпись, эта семантика предписана стандартом.) делает измените битовую комбинацию, установив все старшие 32 бита числа в 1 вместо 0, как вы ожидали:
(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
И если ты пишешь 0xFFFFFFFFFF000000
в десятичном виде вы получите 18446744073692774400.
В качестве заключительного совета всякий раз, когда вы получаете «невозможное» целое число из C или C ++, попробуйте распечатать его в шестнадцатеричном формате; гораздо проще увидеть странности арифметики фиксированной ширины с двумя дополнениями.
Ответ прост — переполнен.
Здесь переполнение произошло в int, и когда вы присваиваете его неподписанному int64, оно преобразуется в 18446744073692774400 вместо 4278190080