Класс с фиксированной точкой - Функции

ОГЛАВЛЕНИЕ

Функции

При помощи этих операций можно легко выполнить множество математических вычислений с использованием класса fixed_point<B, I, F>, например, вычисления, необходимые для перемножения матриц, и т.д. Но класс был бы неполным без других функций, которые стандартная библиотека C++ предоставляет для типов с плавающей точкой, такие как sqrt, или sin, или log, и другие. Хотя можно преобразовать значение fixed_point<B, I, F> в значение с плавающей точкой и затем вызвать соответствующую функцию из стандартной библиотеки C++, это так или иначе, прежде всего, лишило бы смысла класс fixed_point<B, I, F>.

Поэтому были реализованы математические функции для класса fixed_point<B, I, F>. Эти функции достаточно трудно реализовать правильно. Было проделана немалая работа по выбору правильных алгоритмов, но вряд ли их качество реализации столь же высокое, что и у некоторых реализаций стандартной библиотеки C++.

fabs

Функция fabs вычисляет модуль ее аргумента.

friend fixed_point<B, I, F> fabs(fixed_point<B, I, F> x)

{

    return x < fixed_point<B, I, F>(0) ? -x : x;

}

ceil

Функция ceil вычисляет минимальное целое значение, которое не меньше ее аргумента.

friend fixed_point<B, I, F> ceil(fixed_point<B, I, F> x)

{

    fixed_point<B, I, F> result;

    result.value_ = x.value_ & ~(power2<F>::value-1);

    return result + fixed_point<B, I, F>(

        x.value_ & (power2<F>::value-1) ? 1 : 0);

}

floor

Функция floor вычисляет максимальное целое значение, которое не больше ее аргумента.

friend fixed_point<B, I, F> floor(fixed_point<B, I, F> x)

{

    fixed_point<B, I, F> result;

    result.value_ = x.value_ & ~(power2<F>::value-1);

    return result;

}

fmod

Функция fmod вычисляет остаток деления x/y с фиксированной точкой.

friend fixed_point<B, I, F> fmod(fixed_point<B, I, F> x, fixed_point<B, I, F> y)

{

    fixed_point<B, I, F> result;

    result.value_ = x.value_ % y.value_;

    return result;

}

modf

Функция modf разбивает аргумент на целую и дробную части, каждая из которых имеет такой же знак, что и аргумент. Она сохраняет целую часть в объекте, на который указывает ptr, и возвращает дробную часть x/y со знаком.

friend fixed_point<B, I, F> modf(fixed_point<B, I, F> x, fixed_point<B, I, F> * ptr)

{

    fixed_point<B, I, F> integer;

    integer.value_ = x.value_ & ~(power2<F>::value-1);

    *ptr = x < fixed_point<B, I, F>(0) ?

        integer + fixed_point<B, I, F>(1) : integer;

      

    fixed_point<B, I, F> fraction;

    fraction.value_ = x.value_ & (power2<F>::value-1);

  

    return x < fixed_point<B, I, F>(0) ? -fraction : fraction;

}

exp

Функция exp вычисляет экспоненту x.

friend fixed_point<B, I, F> exp(fixed_point<B, I, F> x)

{

    fixed_point<B, I, F> a[] = {

        1.64872127070012814684865078781,

        …

        1.00000000046566128741615947508 };

 

    fixed_point<B, I, F> e(2.718281828459045);

 

    fixed_point<B, I, F> y(1);

    for (int i=F-1; i>=0; --i)

    {

        if (!(x.value_ & 1<<i))

            y *= a[F-i-1];

    }

 

    int x_int = (int)(floor(x));

    if (x_int<0)

    {

        for (int i=1; i<=-x_int; ++i)

            y /= e;

    }

    else

    {

        for (int i=1; i<=x_int; ++i)

            y *= e;

    }

 

    return y;

}

cos

Вычисляет косинус. Алгоритм использует разложение в ряд Маклорена.

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

Сокращение аргумента использует одно деление. Разложение в ряд использует 3 сложения и 4 умножения.

friend fixed_point<B, I, F> cos(fixed_point<B, I, F> x)

{

    fixed_point<B, I, F> x_ = fmod(x, fixed_point<B, I, F>(M_PI * 2));

    if (x_ > fixed_point<B, I, F>(M_PI))

        x_ -= fixed_point<B, I, F>(M_PI * 2);

 

    fixed_point<B, I, F> xx = x_ * x_;

 

    fixed_point<B, I, F> y = - xx *

        fixed_point<B, I, F>(1. / (2 * 3 * 4 * 5 * 6));

    y += fixed_point<B, I, F>(1. / (2 * 3 * 4));

    y *= xx;

    y -= fixed_point<B, I, F>(1. / (2));

    y *= xx;

    y += fixed_point<B, I, F>(1);

 

    return y;

}

sin

Вычисляет синус.Алгоритм использует разложение в ряд Маклорена.

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

Сокращение аргумента использует одно деление. Разложение в ряд использует 3 сложения и 5 умножения.

friend fixed_point<B, I, F> sin(fixed_point<B, I, F> x)

{

    fixed_point<B, I, F> x_ = fmod(x, fixed_point<B, I, F>(M_PI * 2));

    if (x_ > fixed_point<B, I, F>(M_PI))

        x_ -= fixed_point<B, I, F>(M_PI * 2);

 

    fixed_point<B, I, F> xx = x_ * x_;

 

    fixed_point<B, I, F> y = - xx *

        fixed_point<B, I, F>(1. / (2 * 3 * 4 * 5 * 6 * 7));

    y += fixed_point<B, I, F>(1. / (2 * 3 * 4 * 5));

    y *= xx;

    y -= fixed_point<B, I, F>(1. / (2 * 3));

    y *= xx;

    y += fixed_point<B, I, F>(1);

    y *= x_;

 

    return y;

}

sqrt

Функция sqrt вычисляет неотрицательный квадратный корень ее аргумента.

Она вычисляет приближенный квадратный корень при помощи целочисленного алгоритма. Алгоритм описан в Википедии: http://en.wikipedia.org/wiki/Methods_of_computing_square_roots.

Алгоритм происходит из книги по программированию абаков, написанной К. Ву.

Функция возвращает квадратный корень аргумента. Если аргумент отрицательный, функция возвращает 0.

friend fixed_point<B, I, F> sqrt(fixed_point<B, I, F> x)

{

    if (x < fixed_point<B, I, F>(0))

    {

        errno = EDOM;

        return 0;

    }

    fixed_point<B, I, F>::promote_type<B>::type op =

        static_cast<fixed_point<B, I, F>::promote_type<B>::type>(

            x.value_) << (I - 1);

    fixed_point<B, I, F>::promote_type<B>::type res = 0;

    fixed_point<B, I, F>::promote_type<B>::type one =

        (fixed_point<B, I, F>::promote_type<B>::type)1 <<

            (std::numeric_limits<fixed_point<B, I, F>::promote_type<B>

                ::type>::digits - 1);

    while (one > op)

        one >>= 2;

    while (one != 0)

    {

        if (op >= res + one)

        {

            op = op - (res + one);

            res = res + (one << 1);

        }

        res >>= 1;

        one >>= 2;

    }

    fixed_point<B, I, F> root;

    root.value_ = static_cast<B>(res);

    return root;

}

 

Свойства класса std::numeric_limits<>

Класс fixed_point<B, I, F> был бы неполным без специализации шаблона std::numeric_limits<>. Шаблон std::numeric_limits<> позволяет запрашивать информацию о любом числовом типе, такую как его минимальные и максимальные значения, и многую другую. Необходимо написать подлинно универсальный код, независимый от типа и волшебным образом работающий для разных типов.

свойство

тип

значение

has_denorm

const float_denorm_style

denorm_absent

has_denorm_loss

const bool

false

has_infinity

const bool

false

has_quiet_NaN

const bool

false

has_signaling_NaN

const bool

false

is_bounded

const bool

true

is_exact

const bool

true

is_iec559

const bool

false

is_integer

const bool

false

is_modulo

const bool

false

is_signed

const bool

numeric_limits<B> (1)

is_specialized

const bool

true

tinyness_before

const bool

false

traps

const bool

false

round_style

const float_round_style

round_toward_zero

digits

const int

I

digits10

const int

digits

max_exponent

const int

0

max_exponent10

const int

0

min_exponent

const int

0

min_exponent10

const int

0

radix

const int

0

min()

fixed_point<B, I, F>

(2)

max()

fixed_point<B, I, F>

(2)

epsilon()

fixed_point<B, I, F>

(2)

round_error()

fixed_point<B, I, F>

(2)

denorm_min()

fixed_point<B, I, F>

(3)

infinity()

fixed_point<B, I, F>

(3)

quiet_NaN()

fixed_point<B, I, F>

(3)

signaling_NaN()

fixed_point<B, I, F>

(3)

  1. Зависит от базового типа. Если базовый тип B знаковый, то fixed_point<B, I, F> также знаковый. Иначе он беззнаковый.
  2. Эти значения вычисляются на основе параметров шаблона.
  3. Эти значения бессмысленны для типа с фиксированной точкой, и устанавливаются в ноль.

Помощники для отладки программы

Visual Studio поддерживает визуализаторы отладчика, помогающие отладчику подробно отображать переменные. Без специальных визуализаторов отображение по умолчанию переменных типа fixed_point<B, I, F> прежде всего бесполезно, если не выполняется надлежащее преобразование в формат с плавающей точкой вручную.

Выведенное значение -49152 не говорит вам ни о чем существенном, если вы не знаете, что для получения закодированного значения нужно разделить значение -49152.

Но есть кое-что, что визуализатор отладчика может автоматически сделать для вас. При помощи этого визуализатора значение отображается правильно (он не выводит число разрядов после десятичной точки правильно, но это не большая проблема).

Visual Studio сохраняет визуализаторы отладчика в текстовом файле с названием autoexp.dat, в разделе [Визуализатор]. Этот файл можно найти в папке %VSINSTALLDIR%\Common7\Packages\Debugger. Ниже приводится определение визуализатора отладчика для типа fixed_point<B, I, F>:

;------------------------------------------------------------------------------

; fpml::fixed_point

;------------------------------------------------------------------------------

fpml::fixed_point<*,*,*>{

    preview (

        #if ($T3 == 32)( #( $e.value_ / 4294967296., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 31)( #( $e.value_ / 2147483648., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 30)( #( $e.value_ / 1073741824., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 29)( #( $e.value_ / 536870912., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 28)( #( $e.value_ / 268435456., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 27)( #( $e.value_ / 134217728., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 26)( #( $e.value_ / 67108864., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 25)( #( $e.value_ / 33554432., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 24)( #( $e.value_ / 16777216., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 23)( #( $e.value_ / 8388608., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 22)( #( $e.value_ / 4194304., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 21)( #( $e.value_ / 2097152., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 20)( #( $e.value_ / 1048576., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 19)( #( $e.value_ / 524288., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 18)( #( $e.value_ / 262144., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 17)( #( $e.value_ / 131072., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 16)( #( $e.value_ / 65536., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 15)( #( $e.value_ / 32768., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 14)( #( $e.value_ / 16384., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 13)( #( $e.value_ / 8192., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 12)( #( $e.value_ / 4096., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 11)( #( $e.value_ / 2048., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 10)( #( $e.value_ / 1024., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 9)( #( $e.value_ / 512., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 8)( #( $e.value_ / 256., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 7)( #( $e.value_ / 128., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 6)( #( $e.value_ / 64., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 5)( #( $e.value_ / 32., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 4)( #( $e.value_ / 16., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 3)( #( $e.value_ / 8., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 2)( #( $e.value_ / 4., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 1)( #( $e.value_ / 2., " fixed_point ", $T2,".",$T3 ))

        #elif ($T3 == 0)( #( $e.value_ / 1., " fixed_point ", $T2,".",$T3 ))

    )

}

Если вы хотите узнать больше о визуализаторах отладчика для внутреннего кода, ознакомьтесь с подробной документацией по адресу https://svn.boost.org/trac/boost/wiki/DebuggerVisualizers.

Требования к использованию

Тип fixed_point<B, I, F> поступает в форме библиотеки только с заголовками. Не нужно ничего связывать или компоновать. Просто включите fixed_point.h там, где он нужен, и все должно заработать.

Исходный код требует последнюю версию «повышения» (http://www.boost.org/) и считает, что она установлена, и что ее можно найти в ходе (пути) включения. Нужно убедиться, что «повышение» установлено. «Повышение» используется для статических утверждений (утверждений во время компиляции) и проверки понятий, чтобы убедиться, что типы и значения используются по назначению. Кроме того, библиотека операторов повышения используется для автоматического предоставления набора операторов.

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

Пока испытания проведены только с Visual Studio 2005 и 2008, но другие соответствующие стандартам компиляторы также должны работать.

Испытания

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

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

Контрольные задачи

Хотя основной фокус библиотеки – ее использование во встроенных системах с аппаратным обеспечением, не поддерживающим формат с плавающей точкой, были выполнены измерения распределения интервалов времени на платформе ПК. Рассчитывались по времени различные операции, и измеренные временные интервалы сравнивались с типами float и double. Результаты представлены в виде числа периодов тактовых импульсов в расчете на операцию. Эталонная тестовая программа выполняет большое число операций в цикле и считает периоды тактовых импульсов. Общее число периодов тактовых импульсов затем делится на число повторений.

Функция

float

double

15.16

сложение

1.00367

1.00365

1.00373

умножение

1.00368

1.00371

1.00376

деление

1.00369

1.0037

1.0037

квадратный корень

1.00371

1.00371

473.881

синус

1.00364

1.00375

162.533

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