Hex: цвета, байты и память
Откуда в вебе берутся записи вида #FF8800 и почему программисты так любят шестнадцатеричную систему для работы с байтами.
Шестнадцатеричная запись байта — это ровно две цифры из набора 0–9 и A–F, потому что один байт хранит число от 0 до 255, а это в точности диапазон от
00доFFв системе с основанием 16.
Ты уже умеешь переводить числа между двоичной, восьмеричной и шестнадцатеричной системами. Теперь посмотрим, зачем это нужно на практике. Самый наглядный пример — цвета на веб-странице. Когда дизайнер пишет #FF8800, он на самом деле задаёт три числа: сколько красного, зелёного и синего смешать. И запись эта не случайна — она напрямую отражает то, как цвет лежит в памяти компьютера.
Зачем 16-ричная система для байтов
Компьютер хранит данные байтами. Один байт — это 8 бит, то есть 8 двоичных разрядов. Сколько разных значений помещается в байт? Каждый бит даёт два варианта, разрядов восемь, поэтому всего комбинаций:
$$ 2^8 = 256 $$
Значит, байт хранит целое число от 0 до 255 включительно. Записывать такое число в двоичном виде неудобно: 10110010 — попробуй с ходу понять, что это. В десятичном — 178, тоже не сразу видно структуру битов. А вот шестнадцатеричная система ложится на байт идеально. Почему? Потому что одна hex-цифра кодирует ровно 4 бита (полубайт, или «ниббл»):
$$ 16 = 2^4 $$
А раз одна цифра — это 4 бита, то две цифры — это $2 \cdot 4 = 8$ бит, то есть целый байт. Получается чёткое правило: один байт = ровно две шестнадцатеричные цифры. Старшая цифра отвечает за старшие 4 бита, младшая — за младшие. Никаких «иногда три знака, иногда один» — всегда два. Это и делает hex таким удобным.
Как байт распадается на две цифры
Возьмём число 178. Чтобы перевести его в hex, делим на 16 с остатком: $178 = 11 \cdot 16 + 2$. Старшая цифра — 11, в hex это B; младшая — 2. Значит, 178 = B2. Проверим через двоичную запись: 178 — это 1011 0010. Разбиваем на полубайты: 1011 и 0010. Первый полубайт 1011 — это $8+2+1 = 11 = B$, второй 0010 — это 2. Снова B2. Оба пути сходятся.
| Десятичное | Двоичное (8 бит) | Hex (2 цифры) |
|---|---|---|
| 0 | 0000 0000 | 00 |
| 15 | 0000 1111 | 0F |
| 16 | 0001 0000 | 10 |
| 178 | 1011 0010 | B2 |
| 255 | 1111 1111 | FF |
Цвет #RRGGBB как три байта
Цвет на экране собирается из трёх каналов: красного (Red), зелёного (Green) и синего (Blue). Каждый канал — это яркость от 0 (канал выключен) до 255 (канал на максимуме). Три канала по байту — итого три байта, шесть hex-цифр. Вот почему цвет записывается как #RRGGBB: первые две цифры — красный, средние две — зелёный, последние две — синий.
Разберём #FF8800. Красный — FF = 255 (максимум), зелёный — 88 = $8 \cdot 16 + 8 = 136$, синий — 00 = 0. Много красного, средне зелёного, нет синего — получается насыщенный оранжевый. А #FFFFFF — все три канала по 255, это белый; #000000 — все нули, чёрный.
Сколько всего цветов можно так задать? Три байта — это 24 бита, поэтому число различимых оттенков равно:
$$ 256^3 = 2^{24} = 16\,777\,216 $$
Те самые «16 миллионов цветов», про которые пишут на коробках мониторов.
Как это работает
Разберём цвет в коде. Чтобы из строки FF8800 достать три числа, берём по два символа и переводим каждую пару из hex в десятичное. В JavaScript для перевода строки из системы с основанием 16 есть parseInt(текст, 16). Соберём разбор цвета и напечатаем каналы:
function parseColor(hex) {
// убираем '#', если он есть
hex = hex.replace("#", "");
const r = parseInt(hex.slice(0, 2), 16);
const g = parseInt(hex.slice(2, 4), 16);
const b = parseInt(hex.slice(4, 6), 16);
return { r, g, b };
}
const c = parseColor("#FF8800");
console.log("Красный:", c.r);
console.log("Зелёный:", c.g);
console.log("Синий:", c.b);
// и обратно: из трёх байтов собираем hex
function toHex(n) {
return n.toString(16).padStart(2, "0").toUpperCase();
}
const back = "#" + toHex(c.r) + toHex(c.g) + toHex(c.b);
console.log("Обратно:", back);Вывод:
Красный: 255 Зелёный: 136 Синий: 0 Обратно: #FF8800
Обрати внимание на padStart(2, "0"): без него число 0 превратилось бы в одну цифру 0, и цвет собрался бы неправильно. Каждый байт обязан занимать ровно два знака — это то самое правило «байт = две hex-цифры» в действии.
Hex и адреса памяти
Цвета — не единственное место, где удобна шестнадцатеричная. Память компьютера — это длинная лента байтов, и у каждого байта есть номер, адрес. Эти адреса почти всегда показывают в hex: например, 0x7FFE или 0x00400000. Запись 0x в начале — общепринятая пометка «дальше идёт шестнадцатеричное число», как # для цвета.
Почему адреса в hex, а не в десятичной? По той же причине: адрес — это набор байтов, и hex показывает их границы. В числе 0x7FFE каждые две цифры — отдельный байт: 7F и FE. Видно, из скольких байтов состоит адрес и что лежит в каждом. В десятичном виде (32766) эта структура полностью теряется. Поэтому отладчики, дампы памяти, редакторы кодов символов — всё показывает данные в hex.
Частые ошибки
- Путать цифру и её значение. В hex буква
F— это не «буква», а число 15.A= 10,B= 11, ...,F= 15. - Забывать ведущий ноль. Байт 5 в hex — это
05, а не5, если он часть цвета или адреса. Иначе разбивка на байты собьётся. - Считать, что в байте 256 — максимум. Максимум — 255 (это
FF). Значение 256 уже не помещается в один байт, ему нужен второй. - Менять каналы местами. В
#RRGGBBпорядок строгий: красный, зелёный, синий.#FF0000— красный, а#0000FF— синий, не наоборот.
Итоги
- Байт хранит число 0–255, потому что $2^8 = 256$ комбинаций восьми бит.
- Одна hex-цифра — это 4 бита ($16 = 2^4$), поэтому байт всегда записывается ровно двумя hex-цифрами.
- Цвет
#RRGGBB— это три байта: яркость красного, зелёного и синего канала по отдельности. - Всего цветов $256^3 = 2^{24} \approx 16{,}7$ миллиона.
- Адреса памяти тоже пишут в hex (с приставкой
0x) — так видно границы байтов.