Класс с фиксированной точкой - Функции
ОГЛАВЛЕНИЕ
Функции
При помощи этих операций можно легко выполнить множество математических вычислений с использованием класса 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) |
- Зависит от базового типа. Если базовый тип B знаковый, то fixed_point<B, I, F> также знаковый. Иначе он беззнаковый.
- Эти значения вычисляются на основе параметров шаблона.
- Эти значения бессмысленны для типа с фиксированной точкой, и устанавливаются в ноль.
Помощники для отладки программы
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 |
Можно увидеть, что основные операции, такие как сложение, умножение и деление, выполняются быстро, но над функциями все еще нужно работать.