Страницы

Базовые арифметические операции с фиксированной точкой

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

Операции со знаковыми целыми числами и беззнаковыми

Следует отметить, что:

  • Для процессора нет разницы между integer и fractional данными при выполнении операций. Это одни и те же целые числа со знаком и битовый результат операций всегда будет одинаков.

  • Арифметические операции, обрабатывающие беззнаковые целые числа отличаются от операций арифметических операций обрабатывающих целые числа со знаком.

Формат данных

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

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

Операции чтения/записи

Процессор производит арифметичесие операции с данными, которые находятся в регистрах процессора. Для того чтобы данные попали в регистры их необходимо откуда-нибудь получить. Обычно они находятся в памяти компьютера, это может буть внутренняя память процессора, внешняя память или еще что-нибудь.

Данные в регистрах и в памяти могут находится в разных форматах. Например регистры процессора 24-ех разрядные, а память 32-ух разрядная. В этом случае для сохранения 24-битного значения будет использоваться 32 бита в памяти. 24 бита в 32-ух битах можно расположимть минимум 2-умя различными способами (выровненно к старшим или выровненно к младшим разрядам). Для правильного чтения и сохранения данных необходимо использовать соответствующие операции процессора.

Флаги процессора

Многие арифметические операции после выполнения, кроме непосредственного результата вычисления, оставляют "следы" в виде взведенных флагов процессора. Флаги процессора — это однобитные регистры, которые обычно комбинируются в несколько больших регистров стандартной разрядности.

Следует отметить, что флаги всегда устанавливаются или сбрасываются после выполнения операции. То есть значение флага есть один из обязательных результатов выполнения арифметической операции.

Флаг Overflow используется в операциях со знаковыми целыми данными (напоминание: для процессора нет разницы между integer и fractional). Флаг Overflow устанавливается, если знаковый бит "теряется" в процессе выполнения операции. Например Флаг Overflow установится при сложении знаковых целых, если:

  • Два операнда были положительными, а результат операции стал отрицательным

  • Два операнда были отрицательными, а результат операции стал положительным

Флаг Overflow может устанавливаться при выполнении и других операций (например после операции унарный минус).

Флаг Carry используется в операциях с беззнаковыми целыми данными и означает, что для выполнения операции не хватило разрядности регистра. Например, флаг Carry изменяется в случаях если:

  • При выполнении операции сложения беззнаковых целых результат не поместился в регистр результата (Carry Out)

  • Для выполнения операции вычитания беззнаковых целых необходим дополнительный бит (Borrow In)

В некоторых системах флаг Carry устанавливается при ситуации Carry Out и при ситуации Borrow In, а в некоторых устанавливается при Carry Out и сбрасывается при Borrow In. Так же флаг Carry может устанавливаться/сбрасываться и другими арифметическими операциями (например при выполнении операций сдвига).

Операции сложения/вычитания и сравнения

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

Операция сложения знаковых чисел (Add)

Операция сложения производится над операндами, находящимися в регистрах одной и той же разрядности и результат сложения помещает в регистр такой же разрядности, как и регистры операндов. В случае если при выполнения операции сложения присходит "потеря" знака возможны два варианта.

  • Переполнение(Overflow). В этом случае результат сложения будет вычисляться по модулю максимального беззнакового значения. Например, если при сложении положительных чисел выражение A+B будет больше максимального числа MAX_INT, то результат будет вычислен по формуле R=A+BUINT_MAX и знак результата будет отрицательный. Если при сложения двух отрицательных чисел выражение A+B будет меньше MIN_INT, то результат будет вычислен по формуле R=A+B+UINT_MAX и знак результата будет положительный. При возникновении ситуации переполнения ни знак, ни результат не будут правильными.

  • Насыщение(Saturation). При включенном режиме насыщение результат заменяется на максимальное значение INT_MAX, если при сложении положительных чисел результат превысил максимальное значение INT_MAX. В случае если при сложении отрицательных чисел результат становится меньше минимального значения INT_MIN, то результат ограничится этим минимальным значением. При возникновении такой ситуации знак результата будет верный, но значение будет ограничено разрядностью регистра.

По результатам выполнения операции сложения будет установлен флаг Overflow.

Сложение
A B
Float-Point +
Fractional16 +
Результат сложения с переполнением
R
Float-Point
Fractional16
Результат сложения с насыщением
R'
Float-Point
Fractional16
Флаг
Overflow

Операция вычитания знаковых чисел (Sub)

Операция вычитания производится над операндами, находящимися в регистрах одной и той же разрядности и результат вычитания помещает в регистр такой же разрядности, как и регистры операндов. В случае если при выполнения операции вычитания происходит "потеря" знака возможны два варианта, как и при выполнении оперции сложенияпереполнение и насыщение.

Вычитание
A B
Float-Point
Fractional16
Результат вычитания с переполнением
R
Float-Point
Fractional16
Результат вычитания с насыщением
R'
Float-Point
Fractional16
Флаг
Overflow

Операции сравнения (Cmp)

Операции сравнения могут устанавливать разные флаги процессора. Эти флаги используют другие комманды. Например комманды условного перехода или условного исполнения.

Операция унарный минус (Neg)

Операция унарный минус изменяет знак операнда на противоположный. При использовании этой операции следует рассматривать особый случай, когда операция применяется к минимальному значению. Попробуйте предугадать результат выполнения следующей программы на языке С.

#include <stdio.h>

void main()
{
    short x = -32768;
    x = -x;
    printf("%d\n", x);
}

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

Операция абсолютное значение (Abs)

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

#include <stdio.h>
#include <math.h>

void main()
{
    short x = -32768;
    x = abs(x);
    printf("%d\n", x);
}

Если не реализован механизм насыщения, то в результате применения операции абсолютное значение к минимальному знаковому значению мы опять получим минимальное значение (как в приведенной выше программе).

Реализация операций в формате fractional на языке С

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
  
#define FRACTIONAL_Q__BASE (1<<)

#define FRACTIONAL_MAX  ((1<<)-1)
#define FRACTIONAL_MIN  (-1<<)

int Overflow = 0;

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

    R = A + B;

    if (((A ^ B) & FRACTIONAL_MIN) == 0)
    {
        if ((R ^ A) & FRACTIONAL_MIN)
        {
            R = (A < 0) ? FRACTIONAL_MIN : FRACTIONAL_MAX;
            Overflow = 1;
        }
    }

    return R;
}

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

    R = A - B;

    if (((A ^ B) & FRACTIONAL_MIN) != 0) 
    {
        if ((R ^ A) & FRACTIONAL_MIN) 
        {
            R = (A < 0) ? FRACTIONAL_MIN : FRACTIONAL_MAX;
            Overflow = 1;
        }
    }

    return R;
}

int_t neg(int_t A)
{
    int_t R;

    R = (A == FRACTIONAL_MIN) ? FRACTIONAL_MAX : -A;
    return R;
}

int_t abs(int_t A)
{
    int_t R;

    if (A == FRACTIONAL_MIN) {
        R = FRACTIONAL_MAX;
    }
    else 
    {
       if (A < 0)
            R = -A;
       else
            R = A;
    }

    return R;
}

int_t float2fixed_Q_(float x)
{
    x *= FRACTIONAL_Q__BASE;
    x = min(x,FRACTIONAL_MAX);
    x = max(x,FRACTIONAL_MIN);

    return (int_t)x;
}

float fixed2float_Q_(int_t x)
{
    return x/(float)FRACTIONAL_Q__BASE;
}

void main()
{
    float a, b, r;
    int16_t A, B, R;

    // test adds
    a = 0.5f;
    b = 0.5f;

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

    R = adds(A,B);

    r = fixed2float_Q_(R);

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

    // test subs
    B = subs(R,A);

    b = fixed2float_Q_(B);
    printf("%f\n",b);

    // test neg
    a = -1;
    A = float2fixed_Q_(a);

    R = neg(A);

    r = fixed2float_Q_(R);

    printf("-(%f)=%f\n",a,r);
    
    // test abs
    a = -1;
    A = float2fixed_Q_(a);

    R = abs(A);

    r = fixed2float_Q_(R);

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

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