Найти в Дзене
Блокнот математика

Рубрика "Секреты Вим". Байты, коды, символы

Привет, коллеги. Обратимся снова к Юникоду. Внизу ссылки на другие материалы по этой теме.

Мы знаем, что у символов есть коды. Например, у точки код 46, шестнадцатеричный 0х2е (это и есть 46), у буквы Я код 1071 (или 0х42f), у буквы è код 232 (0хe8), и так далее.

Однако код символа не тождественен числовому значению байт, которыми он кодируется в кодировке! Если мы про utf8, то у неё первая половина ASCII совпадает и кодируется одним байтом (первый бит всегда нуль), и код символа совпадает с кодом в кодировке. Так, точка и кодируется байтом 0х2е, он же 00101110 в двоичном виде.

Запомним: символы с шестнадцатеричными кодами от 0 до 7f кодируются одним байтом, у которого первый бит равен нулю. Это дает семь свободных бит и позволяет закодировать 128 символов.

Для символов, для которых нужно два байта, у старшего первые три бита равны 110, а у второго байта первые два бита равны 10. Это оставляет пять бит первого байта и шесть второго, то есть 11 бит, или 2048 символов. Сюда входят символы с диакритикой, кириллица, иврит, арабский, значки вроде ½ и многое другое. Диапазон кодов от 0х80 (шестнадцатеричного) до 0х7ff.

Причина проста. Одиннадцать свободных бит и содержат в себе код символа. Поэтому коды больше 0х7ff в два байта не вмещаются.

Например, у буквы Я код, как уже сказано, 0x42f, а два байта, которые ей соответствуют, это d0 af. Давайте запишем код и байты двоично.

Код: 0100 0010 1111.
Байты:
1101 0000, 1010 1111

Как видим, если убрать 110 в начале первого байта и 10 в начале второго, то получим в точности код без первого (нулевого) бита.

Если нужно три или четыре байта на символ, то принцип аналогичен: у первого байта первые биты 1110 или 11110, а у остальных первые биты всегда 10. Мне четырехбайтовые символы не встречались, а вот трехбайтовые вполне, например эти три значка: ∙ √ ∫ все трехбайтовые. Их коды 0х2219, 0х221а, 0х222b, а байты, соответственно, e2 88 99, e2 88 9a, e2 88 ab.

Диапазон кодов трехбайтных символов от 0х800 до 0xffff (63487 штук), а четырехбайтных от 0х10000 до 0х10ffff (более миллиона, 1048575 штук).

Теперь что нам предлагает Вим для работы с этим всем добром.

Во-первых, мы можем посмотреть код символа командой ga. Она покажет код в десятичной, шестнадцатеричной и восьмеричной системах.

Во-вторых, мы можем посмотреть байты, из которых символ состоит. Это команда g8. Именно так я и определял коды выше.

Обе команды покажут и модифицирующие символы.

В-третьих, команда 8g8 ищет ошибки. Это очень востребовано в восьмибитных кодировках вроде cp1251, если туда попал символ, которого они не знают. Но и в utf8 возможны ошибки (если файл поврежден, например): если после байта со 110 не идет байт с 10 или если байт с 10 идет сам по себе, первым.

Далее, ввести код позволяет команда <C-V> в режиме вставки. После нее можно ввести десятичный код символа, но это только до 255 включительно. Можно нажать u или U и затем шестнадцатеричный код, до четырех (или восьми) байт. Если вам столько не надо, введите любой нецифровой символ, пробел, например.

Пример. Мы хотим ввести символ с кодом 0х2222. Мы в режиме вставки нажимаем <C-V>U2222 и пробел. И получаем ∢.

Есть ловушка, в которую я попал. Если вы вводите код после <C-V> и код этот больше 255, Вим никаких предупреждений не делает! Вы получите символ ÿ с кодом 0хff (что и есть 255), состоящий из байт c3 bf, для любого трехзначного числа, большего либо равного 255. А я искренне полагал, что ÿ имеет код 666. Но нет. Код 0х666 имеет символ ٦ (это "Arabic-indic Digit Six"), десятичный 666 - это 0х29а, и символ ʚ (это "Latin Small Letter Closed Open E"), а восьмеричный 666, это 1000-1 и символ ƶ.

Как ввести символ, зная составляющие его байты, я не знаю. Не знаю простого способа: так-то нам известно, как извлечь из байтов код. Или можно вставить байты в файл физически. Но простого способа не знаю. Но и нужды не вижу...

Ещё есть функции nr2char и char2nr, которые конвертируют код в символ и обратно. Аргумент - число у первой и символ у второй, но возможен второй аргумент. Если он присутствует и это 1, то работа в utf8 независимо от текущей кодировки, а если 0 или опущен, то в текущей кодировке. Если char2nr получит строку, она работает с первым символом. Модифицирующие символы в любом случае считаются отдельными символами.

Применить эти функции можно вот как, например. Иногда "слетает" кодировка, скажем, в почте, и приходит текст с такими вкраплениями:
&# 0F42;. Это указан код символа, а сам символ не мог быть отображен по тем или иным причинам: кодировка не та или шрифта нет. Что ж:

:%s/&#\([0-9]*\);/\=nr2char(submatch(1))/g

Здесь пущена в ход команда замены с вычисляемым выражением замены (на него указывает \= в его начале), уловленный скобками \(...\) номер доступен через submatch(), ну и далее понятно.

Удачи, коллеги.

P.S.: Про Юникод в Виме у меня уже были следующие материалы:

Ввод символов Юникода: способы ввести то, чего нет на клавиатуре.

Ввод юникодного текста: как печатать много символов, которых нет в системной раскладке.

Работа с Юникодом: про Юникод, поиск, комбинирование символов и средства Вим для работы со сложными символами.

Диграфы: средство для ввода символов мнемонической парой.

Поддержка русского, иврита и арабского, греческого и других языков.

Научно-популярные каналы на Дзене: путеводитель
Новости популярной науки12 марта 2022