Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Razdel_3_Predstavlenie_i_obrabotka_chisel_v_kom...docx
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
107.43 Кб
Скачать

9Как выполняется умножение чисел в формате с фиксированной запятой

Умножение с фиксированной точкой может выполняться без хитрых выравниваний и приведения к единой экспоненте. Тем не менее умножение — достаточно опасная операция, которая чаще всего в итоге приводит к потере точности и требует особой аккуратности в обращении. Начнем с математического описания умножения: Пусть имеется два числа a = n1 * 2-q1 и b = n2 * 2-q2.  Тогда: a * b = n1 * 2-q1 * n2 * 2-q2 = n1 * n2 * 2-(q2 + q1). Из выражения видно, что экспоненты чисел при умножении складываются: 2-(q2 + q1). Разрядность данных в этой статье не рассматривается, пока достаточно лишь запомнить, что для безопасного умножения без переполнения и потери точности разрядность результата должна быть не меньше суммарной разрядности сомножителей.  Из-за сложения экспонент результат умножения приходится корректировать для выполнения дальнейших вычислений. При уменьшении экспоненты младшие разряды результата отбрасываются. То есть происходит потеря точности. Можно уменьшить потери точности (и иногда приходится), но способы борьбы с потерями всегда связаны с накладными расходами. Фрагмент кода на С:

int32_t a = 0x8000L; // q15: a = 0.5

int32_t b = 0x100000L; // q20: b = 0.5

int32_t c = 0xC0000L; // q20: c = 0.75

int64_t d; // Временная переменная с увеличенным числом разрядов, чтобы хватило на результат.

d = (int64_t)a * (int64_t)b; // q35 = q15 * q20; d = 0x800000000L (0.25 in q35)

d >>= 15; // q35 / 2 ^ 15 = q20

c += (int32_t)d; // q20: c = 0x100000 (1 in q20)

Отмечу, что 15 младших разрядов результата умножения были отброшены, чтобы привести число к формату слагаемого. Можно было, конечно, увеличить разрядность переменной c, но, как я уже говорил, на практике диапазоны значений обычно ограничены и младшими разрядами умножения зачастую пренебрегают. Кроме того, не учитывается возможность наличия в исходных сомножителях ненулевых старших разрядов.  Но в данной статье обработка переполнений не рассматривается.

10Как выполняется умножение чисел в формате с плавающей запятой

Самые простые операции. Пусть:

X1 = M1qp1

X2 = M2qp2

Тогда:

X1 * X2 = M1qp1 * M2qp2 = M1 * M2 * qp1 * qp2 = (M1 * M2) * qp1+p2

11Как выполняется деление чисел в формате с фиксированной запятой?

Начнем с математического выражения для деления: Пусть имеется два числа a = n1 * 2-q1 и b = n2 * 2-q2.  Тогда: a / b = n1 * 2-q1 / (n2 * 2-q2) = n1 / n2 * 2-(q1 — q2). Сомножитель 2-(q1 — q2) означает, что при выполнении деления экспонента автоматически уменьшается. Если не принять меры, часть значащих разрядов отбрасывается автоматически. Способ коррекции очевиден — необходимо заранее увеличить разрядность делителя настолько, чтобы в результате деления получить желаемое количество значащих бит: a / b = n1 * 2-q1 * 2q3 / (n2 * 2-q2) = n1 / n2 * 2-(q1 — q2 + q3). Таким образом, экспонента частного увеличена на q3 разряда. Фрагмент кода на С:

int32_t a = 0x8000L; // q15: a = 0.5

int32_t b = 0x100000L; // q20: b = 0.5

int32_t c = 0; // q25

int64_t d; // Временная переменная с увеличенным числом разрядов.

d = (int64_t)a << 30; // q45: d = 0x200000000000; (0.5 in q45)

c = (int32_t)(d / (int64_t)b); // q25: c = 0x2000000; (1 in q25)

Очевидно, что при превышении числом разрядности 32 бита, проблему уже не решить так просто. Тем не менее, для простых инженерных расчетов 32-битных чисел обычно более, чем достаточно. Есть один простой способ значительно сократить потерю точности при делении — предварительное нормирование делимого. Нормирование — фактически максимальный сдвиг мантиссы влево, при котором не происходит отбрасывания значащих битов. Определить, на сколько можно сдвинуть число, можно путем подсчета ведущих нулей в делимом, для чего существуют специальные алгоритмы (или даже аппаратные инструкции процессора). После деления частное следует сдвинуть вправо на такое же количество бит для восстановления экспоненты. Вышеприведенный фрагмент кода при этом может выглядеть таким образом:

int32_t a = 0x8000L; // q15: a = 0.5

int32_t b = 0x100000L; // q20: b = 0.5

int32_t c = 0; // q25

int norm_shift = norm(a); // Вычисление нормирующего сдвига. norm_shift = 16

c = ((a << norm_shift) / b); // q(-5): c = 0x800 (1*2^norm in q(-5))

c <<= (30 - norm); // q25: c = 0x2000000; (1 in q25)

Как видим, потери точности в данном случае не произошло и без увеличения разрядности делимого. Впрочем, так происходит не всегда, и если требуется остаться в рамках определенной разрядности (например, 32 бита), приходится реализовывать деление алгоритмически. В обзорной статье вряд ли стоит погружаться в такие дебри — для понимания процесса деления и связанных с ним сложностей достаточно уже приведенного описания.