Любопытная арифметическая ошибка - 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-bitint (максимальное значение обычно 2147483647 на 32-bitint в двух дополнительных представлениях).

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

Другие вопросы по тегам