Здравствуйте, друзья! В прошлый раз мы с вами начали говорить о том, как коды Рида-Соломона помогают обеспечивать необходимый уровень надежности хранения данных. Сегодня остановимся немного подробнее на арифметике полей Галуа, которая используется в расчётах.
Сначала краткий экскурс в прошлую часть. Мы выяснили — чтобы иметь возможность восстанавливать потерянные данные, необходимо сначала определенным образом сгенерировать «избыточные данные». Причем, с математической точки зрения, эта задача (кодирования) сводится к построению некоторой порождающей матрицы и выполнению операции умножения этой матрицы на вектор исходных данных. Процедура восстановления (декодирования), в свою очередь, заключается в обращении порождающей матрицы и умножения её на вектор сохранившихся данных. Схематически это можно изобразить следующим образом.
Процедура кодирования:
Процедура декодирования:
Ограничения арифметики рациональных чисел
Мы уже отмечали некоторые трудности, с которыми приходится сталкиваться при реализации процедур кодирования/декодирования на конкретных аппаратных платформах. Так, например, числа в памяти ЭВМ обычно представлены фиксированным количеством байтов. Как следствие, при умножении элементов порождающей матрицы, можно получить переполнение разрядов. Или при обращении порождающей матрицы в качестве ее элементов могут возникнуть рациональные числа, которые проблематично хранить с произвольной точностью. Поэтому, сегодня мы с вами поговорим о том, как можно реализовать данные процедуры, избегая вышеуказанных проблем. В этом нам поможет теория полей Галуа, о которой мы уже немного говорили в прошлой статье.
Сразу сделаю несколько замечаний. Во-первых, статья прежде всего рассчитана на инженерную аудиторию, поэтому я старался избегать строгих математических определений. Тем, кому хочется более формального введения в данную область, имеет смысл сразу обратиться к соответствующей литературе. Со своей стороны, могу порекомендовать книгу Эрнеста Борисовича Винберга «Курс алгебры». Во-вторых, непосредственно для реализации алгоритмов избыточного кодирования достаточно лишь базового понимания теории конечных полей. Можно просто считать, что заданы некоторые непротиворечивые таблицы умножения, деления, сложения, вычитания и все вычисления происходят с использованием этих таблиц. Это намек для тех, кому не очень хочется глубоко погружаться в данный раздел алгебры.
Поля и арифметические операции в конечных множествах
Прежде чем перейти непосредственно к обсуждению полей Галуа, давайте повторим еще раз, чего мы хотим достичь. Мы хотим иметь возможность умножать матрицы на вектора, обращать матрицы. А самое главное, мы хотим иметь дело только с целыми числами и не переживать за переполнение при выполнении операций умножения и сложения.
Также, во избежание путаницы, имеет смысл сразу разобраться с терминологией. Прежде всего, что такое поле с точки зрения общей алгебры? Как сообщает нам Википедия, «поле — множество, для элементов которого определены операции сложения, вычитания, умножения и деления (кроме деления на нуль), причём свойства этих операций близки к свойствам обычных числовых операций». Что такое поле Галуа? Поле Галуа — это произвольное поле, состоящие из конечного числа элементов. , — стандартное обозначение полей Галуа, где в скобках указывается количество элементов поля.
В современных ЭВМ для хранения чисел обычно используется фиксированное число бит. Нетрудно посчитать, что всего существует различных n-битовых чисел, от 0 и до . Другими словами, мы имеем некоторое конечное множество чисел. На этом множестве мы сейчас попытаемся непротиворечивым образом определить операции умножения, деления, сложения и вычитания. Причем так, чтобы результат любой операции также был -битовым числом! В этом случае говорят, что множество замкнуто относительно введенных операций.
Операция вычитания в системах конечных множеств обычно вводится через понятие нулевого и противоположного элементов. Нулевой элемент — это такой элемент, для которого справедливо равенство: , где – произвольный элемент множества. Элемент является противоположным для , если верно равенство . Обычно противоположный элемент обозначается как . Если мы хотим из вычесть , мы находим противоположный элемент и складываем его с . Соответственно, для того, чтобы иметь возможность вычитать произвольные элементы конечного множества, каждый элемент этого множества должен обладать противоположным.
Аналогичным образом обстоят дела и с операцией деления. Вводится понятие единичного и обратного элементов. Единичный элемент — это такой элемент, для которого верно равенство , где — произвольный элемент множества. Элемент (обозначается как ) называется обратным к , если . Как и при вычитании, если мы хотим разделить на ненулевой , мы должны найти обратный элемент и умножить его на . Для того, чтобы иметь возможность делить произвольные элементы, каждый элемент множества (за исключением нулевого) должен обладать обратным.
Построение полей Галуа GF(p)
Каким образом мы можем определить на конечном множестве чисел указанные арифметические операции, причём так, чтобы множество было замкнуто относительно них? Другими словами, мы хотим произвольное конечное множество элементов превратить в поле Галуа. Первое что приходит в голову, это воспользоваться модульной арифметикой. Тогда если наше множество содержит элементов, то результатом произведения чисел будет является число . Сумма чисел определяется как .
В качестве упражнения, давайте составим таблицы сложения и умножения для множества, состоящего из элементов .
На нижних двух таблицах представлены противоположные и обратные значения для каждого элемента нашего множества. Вооружившись этими таблицами, мы можем выполнять все необходимые нам арифметические операции. Ура!
Построение полей Галуа
Кажется, что мы на верном пути к успеху. Единственное, что нас может пока не устраивать, так это размер нашего поля – 5. Понятно, что для упрощения программной реализации, имеет смысл работать с множествами, содержащими чисел. Тогда мы сможем оперировать полубайтами, байтами, словами и т.д.
На первый взгляд кажется, какая разница, — 5 или 256 элементов в множестве, берём результат операции умножения или сложения по соответствующему модулю и готово. Давайте снова попробуем составить таблицу умножения для множества, но на этот раз содержащего элемента — . Вот что получилось:
Если пристально посмотреть на полученную таблицу, можно обнаружить в ней один серьезный недостаток. Обратите внимание на строчку для элемента 2. В этой строчке нет единичного элемента! Это означает, что мы не сможем делить другие элементы на 2, поскольку для него не существует обратного! Значит, необходимо производить построение таблиц сложения и умножения каким-либо другим способом. Модульная арифметика, к сожалению, больше не работает.
Итак, мы выяснили, что если множество содержит объекта, то модульная арифметика не позволяет задавать непротиворечивые операции умножения. На самом деле, подобные проблемы возникнут для любого множества, количество элементов которого не является простым числом. Но что ещё можно придумать?
Сложение в GF(p^n)
Давайте пока для простоты считать, что множество, на котором мы хотим определить арифметические операции содержит элементов. Это, собственно говоря, числа . Любое число из этого множества можно представить в виде разложения:
По сути дела, это обычное двоичное представление числа. Таким образом, любое число из нашего множества мы можем также рассматривать как вектор длины , элементы которого нули и единицы:
Введём операцию сложения двух любых чисел через сложение соответствующих векторов двоичного разложения. Причем сложение компонентов векторов мы будем вести по модулю 2 (в общем случае, если количество элементов , по модулю ). Таким образом, результирующий вектор также будет двоичным представлением некоторого числа и, как следствие, будет принадлежать нашему множеству (множество будет замкнуто относительно операции сложения).
Несложно заметить, что введенная нами операция сложения эквивалентна битовой операции «исключающее ИЛИ» (XOR). Что очень хорошо, поскольку современные процессоры могут выполнять данную операцию чрезвычайно быстро. Но это еще не все. Очевидно, что для произвольного числа . Это означает, что противоположным элементом к является сам . Таким образом, в поле сложение совпадает с вычитанием!
Умножение в GF(p^n)
Осталось ввести на нашем множестве операцию умножения. Делается это следующим образом. Давайте на время представим, что любое число из нашего множества — это многочлен следующего вида:
Здесь коэффициенты многочлена берутся из двоичного разложения числа . При таком подходе, например, числу будет поставлен в соответствие многочлен .
Операцию умножения двух чисел мы можем определить через умножение многочленов, соответствующих данным числам:
Причем при перемножении все операции с коэффициентами многочленов выполняются по модулю 2.
Но теперь, очевидно, может возникнуть другая проблема. При перемножении двух многочленов степени , может получиться многочлен более высокой степени, который не будет соответствовать ни одному из чисел нашего множества. Это решается следующим образом. Выбирается неприводимый многочлен степени . Грубо говоря, это такой многочлен, который нельзя представить в виде произведения других многочленов, подробнее в статье. После это, используя алгоритм деления многочленов столбиком, который многие могут помнить еще по школьной программе, мы делим результат произведения на неприводимый многочлен. Еще раз напоминаю, что при делении столбиком, операции с коэффициентами многочленов также выполняются по модулю 2. В итоге, получаем многочлен степени, не выше чем , который мы и принимаем за результат произведения двух чисел. Вот пример выполнения умножения двух чисел над полем с использованием неприводимого многочлена . Данный многочлен используется в алгоритме шифрования AES/Rijndael.
Мы рассмотрели поле . Произвольные поля строятся аналогичным образом, только вместо двоичного представления числа используется -ичное и все вычисления проводятся по модулю .
Изоморфизм полей и поля Галуа произвольного размера
А что будет, если выбрать другой неприводимый многочлен? Можно даже поставить более общий вопрос. Предположим, мне вообще не нравятся все эти многочлены и деления столбиком, поэтому я лучше придумаю свою непротиворечивую таблицу умножения используя какой-нибудь другой принцип. Будет ли это чем то, принципиально новым? Ответ — нет. Все эти полученные поля будут изоморфны.
Переводя с «математического» языка, изоморфизм означает, что отличия только в обозначении элементов полей и одно можно свести к другому, выбрав соответствующее правило отображения.
Конечные множества элементов, которые мы рассматривали до сих пор, были двух типов. Первый тип множеств имел число элементов, равное некоторому простому числу . На таких множествах нам удалось задать таблицы умножения и сложения, используя лишь модульную арифметику. Ко второму типу относятся множества, имеющие , — простое, число элементов. На таких множествах возможно задать умножение/сложение используя схему с многочленами. Можно ли задать похожим образом умножение/сложение для множеств произвольного размера, скажем содержащих 6 элементов? Ответ — нет, это невозможно.
Собственно говоря, конечные поля и называются полями Галуа, потому, что он открыл их следующие важные свойства:
- Число элементов конечного поля всегда имеет вид , где простое число, а любое натуральное.
- Все поля размера изоморфны.
Заключение
Вторая статья цикла подходит к концу. В первых двух статьях были представлены краткие теоретические сведения, связанные с кодами Рида-Соломона и полями Галуа. В следующей части планирую более подробно остановиться на особенностях программной реализации упомянутых выше алгоритмов. Спасибо.
Автор: m-a-k-s-i-m