Странный результат после присвоения 2 ^ 31 32-разрядной целочисленной переменной со знаком и без знака

Как следует из заголовка вопроса, присвоение 2 ^ 31 32-разрядной целочисленной переменной со знаком и без знака дает неожиданный результат.

Вот короткая программа (на C++), которую я написал, чтобы увидеть, что происходит:

#include <cstdio>
using namespace std;

int main()
{
    unsigned long long n = 1<<31;
    long long n2 = 1<<31;  // this works as expected
    printf("%llu\n",n);
    printf("%lld\n",n2);
    printf("size of ULL: %d, size of LL: %d\n", sizeof(unsigned long long), sizeof(long long) );
    return 0;
}

Вот результат:

MyPC / # c++ test.cpp -o test
MyPC / # ./test
18446744071562067968      <- Should be 2^31 right?
-2147483648               <- This is correct ( -2^31 because of the sign bit)
size of ULL: 8, size of LL: 8

Затем я добавил к нему еще одну функцию p():

void p()
{
  unsigned long long n = 1<<32;  // since n is 8 bytes, this should be legal for any integer from 32 to 63
  printf("%llu\n",n);
}

При компиляции и запуске меня еще больше смутило вот что:

MyPC / # c++ test.cpp -o test
test.cpp: In function ‘void p()’:
test.cpp:6:28: warning: left shift count >= width of type [enabled by default]
MyPC / # ./test 
0
MyPC /

Почему компилятор должен жаловаться на слишком большое количество сдвигов влево? sizeof(unsigned long long) возвращает 8, значит ли это, что 2 ^ 63-1 - максимальное значение для этого типа данных?

Меня поразило, что, возможно, n * 2 и n ‹< 1 не всегда ведут себя одинаково, поэтому я попробовал следующее:

void s()
{
   unsigned long long n = 1;
   for(int a=0;a<63;a++) n = n*2;
   printf("%llu\n",n);
}

Это дает правильное значение 2 ^ 63 как результат 9223372036854775808 (я проверил это с помощью python). Но что плохого в том, чтобы делать левое дерьмо?

Левый арифметический сдвиг на n эквивалентен умножению на 2 n (при условии, что значение не переполняется)

- Википедия

Значение не переполняется, появится только знак минус, поскольку значение равно 2 ^ 63 (все биты установлены).

Я все еще не могу понять, что происходит с левым переключением, кто-нибудь может объяснить это?

PS: Эта программа была запущена в 32-битной системе под управлением Linux Mint (если это помогает)


person Rushil Paul    schedule 02.04.2012    source источник
comment
Это должно быть unsigned long long n = 1ULL<<31;   -  person kirilloid    schedule 02.04.2012
comment
Бог! это было так просто ?! Почему я не подумал об этом. в любом случае, да 1ULL ‹---------------- 31 действительно работает. тогда спасибо!   -  person Rushil Paul    schedule 02.04.2012


Ответы (3)


В этой строке:

unsigned long long n = 1<<32;

Проблема в том, что литерал 1 имеет тип int, который, вероятно, всего 32 бита. Поэтому сдвиг вытолкнет его за пределы.

Тот факт, что вы сохраняете данные в большем типе данных, не означает, что все в выражении выполняется с этим большим размером.

Итак, чтобы исправить это, вам нужно либо преобразовать его, либо сделать unsigned long long литералом:

unsigned long long n = (unsigned long long)1 << 32;
unsigned long long n = 1ULL << 32;
person Mysticial    schedule 02.04.2012
comment
Что касается последнего предложения: пожалуйста используйте заглавные буквы для обозначения типа. Для многих шрифтов различение 1ll и 111 может быть затруднено, если не невозможно; 1LL ясен и недвусмысленен (и нет суффикса O, чтобы создать проблемы с 0). - person James Kanze; 02.04.2012

Причина сбоя 1 << 32 в том, что 1 не имеет правильного типа (это int). Компилятор не выполняет никакой магии преобразования до того, как на самом деле произойдет само присвоение, поэтому 1 << 32 оценивается с использованием int арифмической функции, выдавая предупреждение о переполнении.

Попробуйте вместо этого использовать 1LL или 1ULL, которые имеют тип long long и unsigned long long соответственно.

person orlp    schedule 02.04.2012

Линия

unsigned long long n = 1<<32;

приводит к переполнению, потому что литерал 1 имеет тип int, поэтому 1 << 32 также является целым числом, которое в большинстве случаев составляет 32 бита.

Линия

unsigned long long n = 1<<31;

также переполняется по той же причине. Обратите внимание, что 1 имеет тип signed int, поэтому на самом деле он имеет только 31 бит для значения и 1 бит для знака. Таким образом, когда вы сдвигаете 1 << 31, он переполняет биты значения, в результате чего получается -2147483648, который затем преобразуется в long long без знака, то есть 18446744071562067968. Вы можете проверить это в отладчике, если проверите переменные и конвертируете их.

Так что используйте

unsigned long long n = 1ULL << 31;
person Lubo Antonov    schedule 02.04.2012