Страницы

Умножение вещественных чисел в формате с фиксированной точкой

Рассмотрим умножение вещественных чисел (a, b — исходные вещественные числа; r — вещественный результат).

r = a×b

Преобразуем вещественные числа из float-point во fractional и распишем операцию умножения подробнее

R = A×B = a×2n1 × b×2n2 = r×2n, где

  • a = A×2−n1 — это множимое; b = B×2−n2 множитель; r = R×2−n — результат операции.

  • a, b, r — это вещественные числа

  • A, B, R — мантиссы (целые числа)

  • n1, n2, n — показатели степени или порядки чисел (целые числа)

Правило перемножения вещественных чисел

При умножении вещественных чисел, которые представлены как мантисса и порядок, мантисы перемножаются, а порядки складываются.

  • Перемножение мантисс: R=A×B

  • Сложение порядков: n=n1+n2

R = A×B = a×2n1 × b×2n2 = r×2n = a×b×2n1+n2

При умножении чисел в формате fractional производится только умножение — R=A×B.

Формат представления вещественных чисел

Выберем формат данных для примеров. Формат Qm.n, где m — количество бит целой части и n — количество бит дробной части. Общее количество бит b=m+n.

Формат данных (Qm.n)
Общее количество бит (b)
Количество бит для целой части (m)
Количество бит для дробной части (n)

Легко заметить, что после выполнения операции умножения формат результата изменяется (было Q., стало Q.) и требует бит в целом (было ).

a = A×2; b = B×2; r = R×2

Для приведения результата операции к тому же формату, что и аргументы, необходимо разделить результат умножения на 2.

Операция перемножения вещественных чисел в формате fractional.

  • Перемножение мантисс: R=A×B

  • Восстановление формата: R=R×2

Деление целых чисел на 2 реализуется с помощью операции арифметического сдвига вправо на .

Также для деление целых чисел на 2 можно использовать операцию арифметического сдвига влево на с последующей операцией выделения старших бит. Выделение старших бит обычно делается простой ассемблерной командой. Так же арифметический сдвиг влево на предпочтительнее делать если производится несколько подряд умножений с накоплением. В этом случае результат вычислений получается более точным.

Пример реализации умножения чисел в формате fractional на языке С.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#define FRACTIONAL_BASE (1<<)

#define INTMAX ((1<<)-1)
#define INTMIN (-1<<)
#define INTMAX ((1U<<)-1)

int_t mul(int_t A, int_t B)
{
    int_t R;

    R = (int_t)A * B;

    if(R == (1<<))
        return INTMAX;
    
    R <<= ;

    return R;
}

int_t getHi(int_t x)
{
    union {
        int_t x;
        struct {
            int_t lo;
            int_t hi;
        };
    }   hl;
    hl.x = x;
    return hl.hi;
}

int_t mul(int_t A, int_t B)
{
    int_t R;

    R = mul(A,B);
    
    return getHi(R);
}

int_t float2fixed(float x)
{
    x *= FRACTIONAL_BASE;
    x = min(x,INTMAX);
    x = max(x,INTMIN);

    return (int_t)x;
}

float fixed2float(int_t x)
{
    return x/(float)FRACTIONAL_BASE;
}

void main()
{
    float a = 0.1f;
    float b = 0.5f;
    float r;
    int16_t A, B, R;

    A = float2fixed(a);
    B = float2fixed(b);

    R = mul(A,B);

    r = fixed2float(R);

    printf("%f*%f=%f\n",a,b,r);
}

Рассмотрим приведенный пример. Для начала вещественные значения a и b конвертируются во fractional A и B с помощью вызова функции float2fixed. При конвертации производится проверка на область допустимых значений с помощью библиотечных float-point функций min/max. Для получения вещественного результата r используется обратная функции fixed2float, в которой используется float-point деление.

При разработке программ, рассчитанных на выполнение в реальном времени, очень важно различать какие операции выполняются с использованием "медленных" библиотечных float-point функций, а какие исполняются "быстрыми" операциями.

Функция умножения mul вызывает функцию mul, которая возвращает битный результат. Внутри функции mul производится собственно умножение чисел, проверка на переполнение и коррекция формата.

Если данные уже сконвертированы в формат fractional, то сами операции умножения и коррекции формата занимают 2-3 ассемблерные инструкции и могут выполняться за один такт процессорного времени!

Умножение fractional на integer

В некоторых случаях необходимо умножать вещественное число на целое число. Рассмотрим, как это сделать используя целочисленную арифметику. Вещественное число представляется в формате fractional, а целое число в формате integer. Значение integer обычно больше единицы (или меньше −1) и его нужно представить в виде fractional мантиссы и integer порядка. Тогда мы сможем умножить исходное вещественное число на мантиссу, а результат сдвинуть на полученный порядок.

Для разложения целого числа на мантиссу и порядок необходимо найти ближайшую степень двойки которая больше или равна исходному целому числу.

Например, если исходное целое — i, нам нужно найти порядок e, который удовлетворяет условию: i≤2e. Это можно сделать с помощью цикла, в котором исходное целое будет делиться на два до тех пора, пока не станет равно нулю. Количество итераций цикла и будет искомый порядок (e). Для нахождения мантиссы s достаточно разделить исходное целое i на 2e. Для вычисления мантиссы (f) в формате fractional можно использовать целочисленное деление (мы его рассмотрим позже).

Результат разложения исходного целого числа на мантиссу и порядок: i=s×2e (если s — float-point) или i=f<<e (если f — fractional)

Разложения целого числа
Целое (i)
Мантисса (s)
Мантисса (f)
Порядок (e)

После разложения целого числа на мантиссу и порядок, нам нужно умножить исходное fractional на мантиссу, а потом умножить полученный результат на 2e. Для умножения на 2e можно использовать арифметический сдвиг влево (записывается, как <<).