Заглянуть «под капот»: понять, как компьютер хранит целые числа
и числа с плавающей точкой, почему возникают переполнения и почему
0.1 + 0.2 != 0.3.
Этот материал не обязателен для написания кода, но даёт глубокое понимание того, как всё устроено.
Компьютер хранит всё в двоичном виде (0 и 1).
Целые положительные числа хранятся как обычное двоичное число.
Например, число 5 — это 0000 0101.
Как хранятся отрицательные числа? В C++ используется дополнительный код.
Правило:
- Берём положительное число в двоичном виде.
- Инвертируем все биты (0 → 1, 1 → 0).
- Прибавляем 1.
Пример для -5 (тип char, 8 бит):
5=0000 0101- Инверсия:
1111 1010 - +1:
1111 1011— это и есть-5.
Почему так сложно? Чтобы процессор мог складывать числа одной и той же операцией +, не задумываясь о знаке.
У каждого типа есть предел (для int это ±2.1 млрд). Что если прибавить 1 к максимальному числу?
int x = 2147483647; // INT_MAX
x = x + 1; // ПЕРЕПОЛНЕНИЕ! (Undefined Behavior в C++)
Число «заворачивается» в отрицательное (если это unsigned) или вызывает неопределённое поведение (если signed).
Дробные числа (float, double) хранятся по стандарту IEEE 754.
Любое число представляется в виде: (-1)^знак * 1.мантисса * 2^экспонента.
В памяти числа раскладываются на три части:
- Знак (1 бит) — 0 (плюс) или 1 (минус).
- Экспонента (8 бит для
float, 11 дляdouble) — степень двойки. - Мантисса (23 бита для
float, 52 дляdouble) — значащие цифры.
В десятичной системе 1/3 нельзя записать точно (0.3333...).
В двоичной системе так же нельзя точно записать 0.1 (это бесконечная дробь).
Компьютер хранит приближение.
Поэтому 0.1 + 0.2 даёт 0.30000000000000004.
Как сравнивать? С заданной точностью (эпсилон):
bool isEqual(double a, double b) {
return fabs(a - b) < 1e-9;
}
- +Infinity / -Infinity — бесконечность (например,
1.0 / 0.0). - NaN (Not a Number) — «не число» (например,
0.0 / 0.0). NaNне равен даже самому себе!NaN == NaN→false.
- representation.cpp — программа, которая показывает биты чисел и переполнение.
Вернуться к основному курсу.