- •Раздел 3 Представление и обработка чисел в компьютере
- •1 В чем отличие позиционных и непозиционных систем счисления? Приведите примеры.
- •2На что указывает основание системы счисления
- •3Назовите алфавит двоичной, восьмеричной и шестнадцатеричной систем счисления
- •4Сформулируйте правила перевода чисел из одной системы счисления в другу
- •5Назовите форматы записи целых и вещественных чисел в памяти эвм.
- •1.1.3 Дополнительный код
- •Обратный код
- •Сложение в обратном коде
- •7Как выполняется сложение чисел в формате с фиксированной запятой
- •8Как выполняется сложение чисел в формате с плавающей запятой
- •9Как выполняется умножение чисел в формате с фиксированной запятой
- •10Как выполняется умножение чисел в формате с плавающей запятой
- •11Как выполняется деление чисел в формате с фиксированной запятой?
- •12Как получить код числа в формате с плавающей запятой по стандарту ieee 754.
- •13Как получить число по его коду в формате с плавающей запятой по стандарту ieee 754
- •14Как выполняется сложение чисел в формате с плавающей запятой по стандарту ieee 754?
- •15Пояснить сущность контроля работы цифровых автоматов по четности, по нечетности
- •16Пояснить сущность контроля работы цифровых автоматов по Хеммингу. Коды Хэмминга.
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 бита), приходится реализовывать деление алгоритмически. В обзорной статье вряд ли стоит погружаться в такие дебри — для понимания процесса деления и связанных с ним сложностей достаточно уже приведенного описания.
