📅 Квітень 2026
⏱ ≈ 12 хв читання
🎯 Початковий–середній рівень
·
Останнє оновлення: 28 травня 2026 р.
Двійкові числа та IEEE 754 — як комп'ютери представляють числа
Кожне число, з яким працює комп'ютер, зрештою зберігається як послідовність двійкових цифр. Розуміння системи числення з основою 2, доповняльного коду для від'ємних цілих і стандарту IEEE 754 — фундамент програмування й розуміння того, чому 0.1 + 0.2 ≠ 0.3.
1. Основи двійкової та шістнадцяткової систем
Позиційні системи числення:
Основа 10 (десяткова): 1027 = 1×10³ + 0×10² + 2×10¹ + 7×10⁰
Основа 2 (двійкова): 1101 = 1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8+4+0+1 = 13
Основа 16 (шістн.): 1A2F = 1×16³ + 10×16² + 2×16¹ + 15×16⁰ = 6703
Десяткова → двійкова (метод ділення на 2):
13 ÷ 2 = 6 ост 1
6 ÷ 2 = 3 ост 0 → читаємо остачі знизу вгору: 1101
3 ÷ 2 = 1 ост 1
1 ÷ 2 = 0 ост 1
Шістн. система використовує цифри 0–9, A–F (10–15).
Кожна шістн. цифра представляє рівно 4 біти:
0x1A2F = 0001 1010 0010 1111
Беззнакові 8-бітні цілі: від 0 до 255 (2⁸ - 1)
Беззнакові 16-бітні: від 0 до 65535
Беззнакові 32-бітні: від 0 до 4 294 967 295
Беззнакові 64-бітні: від 0 до 18 446 744 073 709 551 615
2. Доповняльний код
Домінувальне кодування знакових цілих. Для n-бітного числа старший біт має вагу −2^(n-1) замість +2^(n-1):
Діапазон 8-бітного доповняльного коду: від -128 до +127
Приклади:
0000 0001 = +1
0111 1111 = +127
1000 0000 = -128 (єдине від'ємне значення без додатного відповідника!)
1111 1111 = -1
Перетворення додатного цілого n на -n:
Метод: інвертувати всі біти, потім додати 1.
Приклад: +5 = 0000 0101
інверсія: 1111 1010
+1: 1111 1011 = -5 ✓
Перевірка: 5 + (-5) = 0000 0101 + 1111 1011 = (1)0000 0000 ✓ (перенесення відкидається)
Чому доповняльний код:
• Схема додавання/віднімання однакова для знакових і беззнакових
• Нуль має лише одне представлення (на відміну від прямого коду: +0 і -0)
• Порівняння з нулем: достатньо перевірити знаковий біт
Переповнення в n-бітній арифметиці:
(+127) + 1 = 1000 0000 = -128 (циклічне перенесення — невизначена поведінка в C!)
У більшості мов арифметика за модулем 2ⁿ (Java/JS завжди; C/C++ беззнакові завжди)
3. Побітові операції
AND (&): 0b1100 & 0b1010 = 0b1000 (обидва біти мають бути 1)
OR (|): 0b1100 | 0b1010 = 0b1110 (хоча б один біт є 1)
XOR (^): 0b1100 ^ 0b1010 = 0b0110 (рівно один біт є 1)
NOT (~): ~0b1100 = 0b0011 (інвертувати всі біти)
Зсув ліворуч (<>n): x >> n = x / 2ⁿ (арифметичний, зберігає знак)
Беззнаковий зсув праворуч (>>>n): (заповнення нулями, завжди невід'ємний)
Поширені трюки маніпуляції бітами:
─────────────────────────────────────────────────────
Перевірити біт k: (x >> k) & 1
Встановити біт k: x | (1 << k)
Скинути біт k: x & ~(1 << k)
Перемкнути біт k: x ^ (1 << k)
Перевірка степеня 2: n && !(n & (n-1))
Найнижчий встановл. біт: x & (-x) [виділити LSB]
Скинути найнижчий: x & (x-1) [у циклах popcount]
Округлити до наст. степ.2: --n; n|=n>>1; n|=n>>2; n|=n>>4; n|=n>>8; n|=n>>16; ++n
Обмін без temp: a^=b; b^=a; a^=b [трюк обміну через XOR]
Абсолютне значення: mask = n>>31; (n^mask) - mask
─────────────────────────────────────────────────────
Застосування: визначення розміру геш-таблиць (наступний степінь 2), розміри груп потоків GPU,
стиснення, шифрування, швидка маніпуляція пікселями.
4. Рухома кома IEEE 754
Стандарт IEEE 754 (1985, переглянутий 2008) визначає, як дійсні числа зберігаються у двійковому вигляді. Два основні формати:
Одинарна точність (32 біти, float):
біт 31: знак s (0=додатне, 1=від'ємне)
біти 30–23: порядок e (8 бітів, зміщення на 127)
біти 22–0: мантиса m (23 біти, неявна провідна 1)
Значення (нормальне): (-1)^s × 1.m × 2^(e-127)
Подвійна точність (64 біти, double / JS Number):
біт 63: знак s
біти 62–52: порядок e (11 бітів, зміщення на 1023)
біти 51–0: мантиса m (52 біти, неявна провідна 1)
Значення (нормальне): (-1)^s × 1.m × 2^(e-1023)
Приклад: 0.1 у double:
s = 0
1.0001100110011... × 2^(-4) [1/10 не є скінченним у системі з основою 2!]
Зберігається як: 0 01111111011 1001100110011001100110011001100110011001100110011010
Фактично збережене значення: 0.1000000000000000055511151231257827021181583404541015625
Саме тому: 0.1 + 0.2 = 0.30000000000000004 у більшості мов.
Діапазон і точність подвійної точності:
Найменше додатне нормальне: ~2.225 × 10⁻³⁰⁸
Найбільше нормальне: ~1.798 × 10³⁰⁸
Точність: ~15–17 значущих десяткових цифр
"Машинний епсилон": ε = 2⁻⁵² ≈ 2.22 × 10⁻¹⁶ (1 ULP при величині 1)
5. Спеціальні значення: NaN, Inf, денормалі
Спеціальні бітові комбінації в IEEE 754:
Порядок = усі 1 (255 для float, 2047 для double):
мантиса = 0: ±нескінченність (напр., 1.0/0.0 = +Inf)
мантиса ≠ 0: NaN (Not a Number, не число) (напр., 0.0/0.0, sqrt(-1.0))
Порядок = усі 0:
мантиса = 0: ±нуль (існують і +0, і -0; +0 == -0, але 1/+0 ≠ 1/-0)
мантиса ≠ 0: денормаль (субнормаль) — розширює діапазон поблизу нуля
Значення: (-1)^s × 0.m × 2^(-1022) [без неявної провідної 1]
Жертвує швидкістю заради поступового зникнення порядку
Властивості NaN (IEEE 754):
• NaN ≠ NaN (єдине значення, що не дорівнює саме собі)
• Перевірка: isNaN(x) у JS / std::isnan(x) у C++
• Будь-яка операція з NaN дає NaN (NaN "поширюється")
• NaN не впорядкований: ні NaN < x, ні NaN >= x не є істинними
Арифметика нескінченності:
+Inf + скінченне = +Inf ✓
+Inf - +Inf = NaN (невизначеність ∞ - ∞)
+Inf × 0 = NaN (невизначеність 0 × ∞)
скінченне / +Inf = ±0 ✓
Денормалі: важливі для числової стабільності поблизу нуля.
На деякому обладнанні операції з денормалями у 100× повільніші (режим flush-to-zero).
6. Точність і округлення
Не всі цілі можна точно представити в подвійній точності!
Double має 53 значущі біти → точні цілі до 2⁵³ = 9 007 199 254 740 992
Проблема: 9007199254740993 + 1 = 9007199254740992 (хибно — втрачено +1)
Катастрофічне скорочення:
x = 1234567890123456.5
y = 1234567890123456.0
x - y = ? → мало б бути 0.5, але обидва округлено до одного double → 0.0 !
Компенсоване підсумовування Кагана:
Для підсумовування багатьох float, відстежуючи "втрачену" частину c:
float sum = 0, c = 0;
для кожного x:
float y = x - c;
float t = sum + y;
c = (t - sum) - y;
sum = t;
Похибка O(ε) замість O(n·ε) для наївного підсумовування.
Режими округлення (IEEE 754):
• До найближчого, нічию до парного (за замовчуванням, "банківське округлення")
• До +∞
• До -∞
• До нуля (відкидання)
Інтервальна арифметика використовує округлення до +∞ і -∞ для гарантованих меж.
7. Пастки чисел у JavaScript
JavaScript має єдиний числовий тип: IEEE 754 double. Це призводить до деякої несподіваної поведінки:
// Усі числа JavaScript — 64-бітні double за IEEE 754
0.1 + 0.2 === 0.3 // false (0.30000000000000004)
Number.MAX_SAFE_INTEGER // 9007199254740991 (2^53 - 1)
Number.EPSILON // 2.220446049250313e-16
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324 (найменше додатне, денормаль)
// Перевірка безпечного цілого
Number.isSafeInteger(9007199254740991) // true
Number.isSafeInteger(9007199254740992) // false!
// Порівняння чисел з рухомою комою
function approxEqual(a, b, eps = Number.EPSILON * 4) {
return Math.abs(a - b) <= eps * Math.max(Math.abs(a), Math.abs(b), 1);
}
// BigInt для цілих довільної точності (ES2020+)
const big = 9007199254740993n; // точно!
big + 1n === 9007199254740994n; // true ✓
// Побітові операції перетворюють на int32, а потім назад на double!
2147483648 | 0 // -2147483648 (2^31 переходить у від'ємний int32)
0xFFFFFFFF | 0 // -1 (те саме)
0xFFFFFFFF >>> 0 // 4294967295 (беззнаковий -- використовуйте >>> для uint32)
Використовуйте
BigInt для великих точних цілих і decimal.js / bignumber.js для точної десяткової арифметики у фінансових обчисленнях. Ніколи не порівнюйте числа з рухомою комою на точну рівність — завжди застосовуйте допуск, пропорційний до величини.