Не понимаю разницу между signed / unsigned
Не понимаю разницу между signed и unsigned, читал много материала в интернете, но вот как-то не получается понять. Зачем используют в коде эти ключевые слова и что они дают?
На одном из сайтов написано:
The term "unsigned" in computer programming indicates a variable that can hold only positive numbers.
Но ведь я могу сделать так:
unsigned int x = -100;
И компилятор не выдаст ошибку, то есть беззнаковый всё таки может хранить отрицательное значение? Или я что-то упустил? Все объяснения в интернете уходит сразу в -127 to 127 и 0 to 256, но это не дает мне понимания. Объясните, пожалуйста, русским языком.
Ответы (3 шт):
Разница в том, что при для знаковых и беззнаковых чисел процессор использует разные операторы для умножения, деления и сравнения на больше/меньше.
Например, для большинства платформ signed char(-1) и unsigned char(255) имеют в памяти представление 0xFF
Но signed char(0xFF) < 127, а unsigned char(0xFF) > 127
signed char(0xFF) * 100 = -100, а unsigned char(0xFF) * 100 = 25500
И так далее
тип int может принимать значения от −2 147 483 648 до 2 147 483 647 (или от –32768 до 32767), а
тип unsigned int может принимать значения от 0 до 4 294 967 295 (или от 0 до 65535)
действительно, вы можете сделать так:
unsigned int x = -100;
но тогда произойдёт переполнение. Рассмотрим пример (тип short занимает 16 бит):
unsigned short x = 65535; // наибольшее значение, которое может хранить 16-битная unsigned переменная
std::cout << "x was: " << x << std::endl;
x = x + 1; // 65536 - это число больше максимально допустимого числа из диапазона допустимых значений. Следовательно, произойдёт переполнение, так как переменнная x не может хранить 17 бит
std::cout << "x is now: " << x << std::endl;
Вывод:
x was: 65535
x is now: 0
Что случилось? Произошло переполнение, так как мы попытались присвоить переменной x значение больше, чем она способна в себе хранить.
Число 65 535 в двоичной системе счисления представлено как 1111 1111 1111 1111. 65 535 — это наибольшее число, которое может хранить 2-байтовая (16 бит) целочисленная переменная без знака, так как это число использует все 16 бит. Когда мы добавляем 1, то получаем число 65 536. Число 65 536 представлено в двоичной системе как 1 0000 0000 0000 0000, и занимает 17 бит! Следовательно, самый главный бит (которым является 1) теряется, а все 16 бит справа — остаются. Комбинация 0000 0000 0000 0000 соответствует десятичному 0, что и является нашим результатом.
TL; DR
Знаковые и беззнаковые типы имеют один и тот же размер, одни и те же требования выравнивания, а отличаются они в основном «смыслом» какого-то одного бита: у первых он означает знак числа, а у вторых — само значение.
unsigned int x = -100;
Ошибку компилятор не выдает, потому что в C слабая типизация и -100 неявно конверируется в unsigned int.
Для начала приведу несколько выдержек из стандарта (следует читать по-порядку):
Существует пять стандартных знаковых целочисленных типа:
signed char,short int,int,long intиlong long int. <…>— ISO/IEC 9899:2017 § 6.2.5 / 4
Для каждого такого знакового типа существует соответствующий беззнаковый тип, что обозначается ключевым словом
unsigned. Этот тип имеет тот же размер (включая информацию о знаке) и требования выравнивания. <…>— ISO/IEC 9899:2017 § 6.2.5 / 6
Для любого беззнакового типа (кроме
unsigned char), биты объектного представления делятся на две группы: значимые биты и padding-биты. Если в типе N значимых бит, то он должен быть способен представлять значения от 0 до 2N − 1 в двоичной системе счисления.— ISO/IEC 9899:2017 § 6.2.6.2 / 1
Для любого знакового типа биты объектного представления делятся на три группы: значимые биты, padding-биты и знаковый бит. В любом типе должен быть всего один знаковый бит. <…> Если он равен нулю, то результирующее значение не меняется. Если же он равен единице, значение должно быть модифицированно одним из следующих способов:
- соответствующее значение с нулевым знаковым битом инвертируется (sign and magnitude);
- знаковый бит имеет значение −(2M) (two's complement);
- знаковый бит имеет значение —(2M − 1) (one's complement).
<…>
— ISO/IEC 9899:2017 § 6.2.6.2 / 2
Таким образом отличие знаковых типов от беззнаковых заключается, собственно, в бите знака: в первых он есть, а во вторых — нет.
И компилятор не выдаст ошибку, то есть беззнаковый всё таки может хранить отрицательное значение?
Нет, беззнаковое число не может хранить отрицательное значение, потому что в нем нет бита знака. А ошибку компилятор не выдает, потому что выполняется неявное преобразование -100 в тип unsigned int.
Мы можем присвоить вещественное число переменной типа int:
int a = 3.14;
Но это ведь не значит, что переменные типа int могут хранить дробные значения?