Любопытная арифметическая ошибка - 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
Кто-нибудь может сказать мне, как это возможно?
5 ответов
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
включает в себя поведение, определяемое реализацией, но так как никто не создавал ЦП с единичным дополнением или знаковым значением в течение многих лет, все реализации, с которыми вы, вероятно, столкнетесь, определяют его точно так же.) Я написал приведение в стиле C, потому что все, что я здесь говорю, в равной степени относится к 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
Я пишу шестнадцатеричное число там, без u
суффикс, чтобы подчеркнуть, что битовая комбинация значения не изменяется при преобразовании его в тип со знаком; это только переосмыслено.
Теперь, когда вы назначаете -16777216 uint64_t
переменная, она преобразуется обратно без знака как-будто путем добавления 2 64. (В отличие от преобразования без знака в подпись, эта семантика предписана стандартом.) Это действительно изменяет битовую комбинацию, устанавливая все старшие 32 бита числа в 1 вместо 0, как вы ожидали:
(uint64_t) (int) (255u*256u*256u*256u) = 0xFFFFFFFFFF000000u
И если ты пишешь 0xFFFFFFFFFF000000
в десятичном виде вы получите 18446744073692774400.
В качестве заключительного совета всякий раз, когда вы получаете "невозможное" целое число из C или C++, попробуйте распечатать его в шестнадцатеричном формате; гораздо проще увидеть странности арифметики фиксированной ширины с двумя дополнениями.
Здесь переполнение произошло в int, и когда вы присваиваете его неподписанному int64, оно преобразуется в 18446744073692774400 вместо 4278190080