Почему "unsigned int x = -100" выдаёт значение 4294967196?
Почему unsigned int x = -100
выдаёт значение 4294967196
?
Ответы (7 шт):
По той же причине, по которой
int summ(int a, int b) { return a + b; }
int main() {
std::cout << summ(1.5, 1.5) << "\n";
}
работает (хоть и весьма неожиданно). По той же причине, по которой
if (42) {
...
}
эквивалентен
if (true) {
...
}
Неявное преобразование типов https://en.cppreference.com/w/cpp/language/implicit_conversion
Компилятор c++ "исходит из логики", что программист "знает что делает". Поэтому ведёт себя, как помощник, а не как контролёр. Есть ряд определённых допущений, которые позволяет себе делать компилятор. Но не всё подряд. Например при инициализации переменных списком инициализации сужающие преобразования недопустимы
unsigned int x { -100 }; // error
Плюсы - язык со статической типизацией, но не со строгой. Это даёт свои "плюшки" при написании кода и его оптимизации компилятором, но накладывает некоторую ответственность на программиста. Следить за соответствием типов, границами обрабатываемых диапазонов - это зона ответственности программиста.
Добавлено: Справедливости ради нужно отметить, что непосредственно самого объяснения почему -100 - это именно 4294967196 в моём ответе нет. Но объяснение, почему это так "на уровне байт" уже дали до меня и после меня. Да, всё дело в способе представления значения того или иного типа в памяти. Так исторически (по весьма объективным причинам) сложилось, что представление положительного числа целочисленных типов (со знаком) и представление беззнакового целочисленного типа эквивалентны. А представление отрицательных чисел таково, что оно зависит от количества бит/байт, выделенных для хранения данного типа. Разработчики стандарта не стали, естественно, изобретать что-то этакое и внесли в стандарт требования, которые буквально согласуются с "логикой машин". Довольно подробно со ссылками на стандарт и разложением чисел на битики это описано здесь:
Хочу обратить ваше внимание, что когда размеры типов при преобразованиях разные - это уже не всегда простое побитовое копирование. И даже когда это просто копирование всех бит, оно может происходить на уровне инструкций процессора с отсечением. Всё это не важно. Важно - что об этом говорит стандарт самого языка, и что гарантированно им! Конкретно в случае unsigned int x = -100
:
результатом является значение целевого типа, которое сопоставимо с исходным целым числом по модулю 2^n, где n — ширина целевого типа (количество бит).
Как этого добивается компилятор, какие именно инструкции под конкретную архитектуру он выбирает - это дело второе.
unsigned int
- это безнаковый тип, который не может быть меньше 0
.
4294967196
берётся потому, что это максимальное число допустимое данным типом от которого отнимается 100
. Хотя принимаемое значение может отличаться в зависимости от компилятора.
Проиллюстрирую наглядно на Python
, там внутри всё тоже на C
на самом деле сделано. Да и вообще это кросс-языковая история.
import struct
data = struct.pack('>i', -100) # упаковываем -100 в 4 байта как int32
print(data.hex().upper()) # печатаем эти байты в 16-ричном виде
print(struct.unpack('>I', data)[0]) # распаковываем как uint32 и печатаем
Вывод:
FFFFFF9C
4294967196
То есть смысл в чём. -100
- это знаковый тип int
, размером 4 байта. Его 16-ричное представление в памяти - это FFFFFF9C
. Когда происходит присваивание этого же значения в беззнаковый тип int
, то берётся байтовое представление числа и воспринимается так, как будто туда было предварительно записано число без знака. Такое число без знака, которое соответствует байтовому представлению FFFFFF9C
- это как-раз 4294967196
.
Вообще система такая. Беззнаковые числа используют все 4 байта полностью. А у знаковых чисел для положительных чисел старший бит = 0, для отрицательных старший бит = 1. И счёт идёт от 0 в обратную сторону.
Т.е. 00000000
- это 0
и для знакового и для беззнакового.
А вот FFFFFFFF
- это беззнаковое 4294967295
и знаковое -1
.
Попробуй запустить эту программу:
#include <stdio.h>
#define MIN_CHAR_VALUE -128
#define MAX_CHAR_VALUE 127
int main()
{
// char хранит один байт
char i = MIN_CHAR_VALUE;
for (; i < MAX_CHAR_VALUE; i++)
{
printf("signed: %d, unsigned: %u\n", i, (unsigned char) i);
}
printf("signed: %d, unsigned: %u\n", i, (unsigned char) i);
return 0;
}
Этот код выводит все значения i
в знаковом и беззнаковом виде.
Чтобы не мучиться, вот результат:
signed: -128, unsigned: 128
signed: -127, unsigned: 129
signed: -126, unsigned: 130
...
signed: -8, unsigned: 248
signed: -7, unsigned: 249
signed: -6, unsigned: 250
signed: -5, unsigned: 251
signed: -4, unsigned: 252
signed: -3, unsigned: 253
signed: -2, unsigned: 254
signed: -1, unsigned: 255
signed: 0, unsigned: 0
signed: 1, unsigned: 1
signed: 2, unsigned: 2
signed: 3, unsigned: 3
...
signed: 119, unsigned: 119
signed: 120, unsigned: 120
signed: 121, unsigned: 121
signed: 122, unsigned: 122
signed: 123, unsigned: 123
signed: 124, unsigned: 124
signed: 125, unsigned: 125
signed: 126, unsigned: 126
signed: 127, unsigned: 127
Сначала, когда у нас было максимальное отрицательное значение, у беззнакового значение было на 1 больше максимального знакового.
Знаковое приближалось к -1, а беззнаковое — к 255.
Затем знаковое стало равным 0, и беззнаковое тоже стало нулём.
Далее значения приближались к максимальному знаковому (127) как у знакового, так и у беззнакового типов.
То же самое будет для int
, short
и т.д.
Отрицательное целое -100 приводится к беззнаковому типу unsigned int
. В этом случае стандарт C требует прибавлять к нему 232 до тех пор, пока новое значение не окажется в диапазоне [0, 232 - 1]. Достаточно одного прибавления, получится -100 + 232 = 4294967196.
На старых машинах/компиляторах возможна ситуация когда UINT_MAX = 65535
. Тогда значение будет -100 + 216 = 65436. Если unsigned int
занимает больше тридцати двух бит, результат тоже будет другой.
Короче, беззнаковое значение вычисляется по модулю UINT_MAX + 1
.
C
Из черновика стандарта С11 6.3.1.3 Signed and unsigned integers:
2 Иначе, если новый тип беззнаковый, значение преобразуется последовательным добавлением или вычитанием значения на единицу большего максимального значения нового типа до тех пор, пока значение не окажется в диапазоне значений нового типа. 60)
…
60) Правила описывают математические операции, не операции данного типа.
Оригинал:
2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type. 60)
…
60) The rules describe arithmetic on the mathematical value, not the value of a given type of expression.
C++
Из C++ reference, Implicit conversions, Integral conversions:
- Если результирующий тип беззнаковый, результирующее значение – наименьшее беззнаковое значение равное исходному по модулю 2n, где n – число бит результирующего типа.
Оригинал:
- If the destination type is unsigned, the resulting value is the smallest unsigned value equal to the source value modulo 2n where n is the number of bits used to represent the destination type.
P.S. Все используют черновик стандарта, потому что сам стандарт платный, а черновик очень мало от него отличается.
P.P.S. Спасибо wololo за его комментарий, из которого и сделан этот ответ.
Основная ваша тема такова: "Почему "unsigned int x = -100" выдаёт значение 4294967196?". Что ж, ответ очень прост: так как вы явно прописываете тип переменной 'unsigned int',то максимальный диапазон типа int увеличивается вдвое, при этом минимальный равен 0 (не меньше!!). Это важно знать и понимать. То есть, так как вы в переменную 'x' инициализируете числом '-100', то в результате выдается мусор ('4294967196'), потому что, как я уже ранее написал, в типе unsigned нельзя заносить числа, которые меньше нуля. Из-за чего выводится мусор. А если чисто прописать тип данных 'int', то естественно, все будет корректно, то есть никакого мусора не будет. Ведь минимальный диапазон этого типа равен -2147483647.