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

По мере чтения заголовка вопроса назначение 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, не всегда ведут себя одинаково, поэтому я попробовал это: </p>

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 (если это помогает)

Ответы [ 3 ]

10 голосов
/ 02 апреля 2012

На этой строке:

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;
5 голосов
/ 02 апреля 2012

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

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

3 голосов
/ 02 апреля 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, который затем преобразуется в длинный без знака, который равен 18446744071562067968.Вы можете проверить это в отладчике, если вы проверяете переменные и конвертируете их.

Поэтому используйте

unsigned long long n = 1ULL << 31;
...