Класс с фиксированной точкой - Операторы
ОГЛАВЛЕНИЕ
Операторы
Разумеется, чтобы была возможность делать что-либо полезное с объектами fixed_point<B, I, F>, необходимо несколько операторов.
Присваивание и преобразование
Имеется простой оператор присваивания, реализованный в виде конструктора копирования, и операция перестановки.
fixed_point<B, I, F> & operator =(fixed_point<B, I, F> const& rhs)
{
fixed_point<B, I, F> temp(rhs);
swap(temp);
return *this;
}
Сама операция перестановки передает полномочия перестановке из стандартной библиотеки C++.
void swap(fixed_point<B, I, F> & rhs)
{
std::swap(value_, rhs.value_);
}
Имеется также версия присваивания, способная выполнять преобразования между различными форматами с фиксированной точкой, и поэтому также нужен преобразующий конструктор копирования. Преобразование между различными форматами с фиксированной точкой может выполняться путем сдвига представления влево или вправо при необходимости.
template<unsigned char I2, unsigned char F2>
fixed_point<B, I, F> & operator =(fixed_point<B, I2, F2> const& rhs)
{
fixed_point<B, I, F> temp(rhs);
swap(temp);
return *this;
}
template<unsigned char I2, unsigned char F2>
fixed_point(fixed_point<B, I2, F2> const& rhs)
: value_(rhs.value_)
{
if (I-I2 > 0)
value_ >>= I-I2;
if (I2<I > 0)
value_ <<= I2-I;
}
Сравнение
Для сравнения реализованы операторы operator < и operator ==.
bool operator <(fixed_point<B, I, F> const& rhs) const
{
return value_ < rhs.value_;
}
bool operator ==(fixed_point<B, I, F> const& rhs) const
{
return value_ == rhs.value_;
}
Класс boost::ordered_field_operators автоматически реализует операторы operator >, operator >=, и operator <= на основе operator <, а также operator != на основе operator ==.
В форме записи псевдокода автоматическое предоставление операторов выглядит примерно так:
boost::ordered_field_operators | |
operator <(a, b) |
|
operator >(a, b): | return operator <(b, a); |
operator >=(a, b): | return ! operator <(a, b); |
operator <=(a, b): | return ! operator <(b, a); |
operator ==(a, b) |
|
operator !=(a, b): | return ! operator ==(a, b); |
Преобразование в логический тип
Типы с плавающей точкой float и double поддерживают преобразование в логический тип. Значение с плавающей точкой преобразуется в истину, когда оно != 0, и в противном случае оно преобразуется в ложь. Итак, было реализовано преобразование в логический тип, и operator !, возвращающий противоположное значение.
operator bool() const
{
return (bool)value_;
}
bool operator !() const
{
return value_ == 0;
}
Одноместный оператор –
Для знаковых типов с фиксированной точкой можно применять одноместный минус-оператор для получения инверсии относительно сложения. Для беззнаковых типов с фиксированной точкой эта операция не определена. Разделенное с целочисленным базовым типом B, минимальное значение, представимое данным типом, не может быть инвертировано, так как оно может выдать положительное значение, которое выходит за пределы допустимого диапазона и не может быть представлено.
fixed_point<B, I, F> operator -() const
{
fixed_point<B, I, F> result;
result.value_ = -value_;
return result;
}
Инкремент и декремент
Типы с плавающей точкой могут быть увеличены или уменьшены на 1.
fixed_point<B, I, F> & operator ++()
{
value_ += power2<F>::value;
return *this;
}
fixed_point<B, I, F> & operator --()
{
value_ -= power2<F>::value;
return *this;
}
Класс boost::unit_steppable автоматически реализует операторы operator ++(int) (последующее увеличение) и operator -–(int) (последующее уменьшение) в виде operator ++ и operator --.
В форме записи псевдокода автоматическое предоставление операторов выглядит примерно так:
boost::unit_steppable | |
operator ++() |
|
operator ++(int): | tmp(*this); ++tmp; return tmp; |
operator --() |
|
operator --(int): | tmp(*this); --tmp; return tmp; |
Сложение и вычитание
Сложение и вычитание реализованы для fixed_point<B, I, F> в виде операторов operator += и operator -=.
fixed_point<B, I, F> & operator +=(fixed_point<B, I, F> const& summand)
{
value_ += summand.value_;
return *this;
}
fixed_point<B, I, F> & operator -=(fixed_point<B, I, F> const& diminuend)
{
value_ -= diminuend.value_;
return *this;
}
Класс boost::ordered_field_operators автоматически реализует операторы operator + и operator – в виде operator += и operator -=.
В форме записи псевдокода автоматическое предоставление операторов выглядит примерно так:
boost:: ordered_field_operators | |
operator +=(s) |
|
operator +(s): | tmp(*this); tmp += s; return tmp; |
operator -=(d) |
|
operator -(d): | tmp(*this); tmp -= d; return tmp; |
Со сложением и вычитанием нужно быть внимательным, так как из-за наследования ими целочисленной реализации они могут переполняться.
Умножение и деление
Умножение и деление реализованы для fixed_point<B, I, F> в виде опеаторов operator *= и operator /=.
fixed_point<B, I, F> & operator *=(fixed_point<B, I, F> const& factor)
{
value_ = (static_cast<fpml::fixed_point<B, I, F>::promote_type<B>::type>
(value_) * factor.value_) >> F;
return *this;
}
fixed_point<B, I, F> & operator /=(fixed_point<B, I, F> const& divisor)
{
value_ = (static_cast<fpml::fixed_point<B, I, F>::promote_type<B>::type>
(value_) << F) / divisor.value_;
return *this;
}
Класс boost::ordered_field_operators автоматически реализует операторы operator * и operator / в виде operator *= и operator /=.
В форме записи псевдокода автоматическое предоставление операторов выглядит примерно так:
boost:: ordered_field_operators | |
operator *=(s) |
|
operator *(s): | tmp(*this); tmp *= s; return tmp; |
operator /=(d) |
|
operator /(d): | tmp(*this); tmp /= d; return tmp; |
Реализация умножения и деления представляет собой небольшие трудности. Вероятно, вы помните: класс fixed_point<B, I, F> имеет член value_ типа B, имеющий целочисленный тип.
Теперь рассмотрим, что происходит, когда перемножаются два целых числа, каждое из которых имеет длину 8 битов. Получится результат длиной в 16 битов. Крайний случай – возведение в квадрат наибольшего представимого значения, например:
a = 255 = 0xFF
a*a = 65025 = 0xFE01
Ясно видно, что для того, чтобы иметь возможность хранить любой возможный результат умножения, потребуется 16 битов, что в два раза превышает число битов в исходных множителях. Иными словами: два множителя при перемножении друг на друга дают результат, длина которого в два раза превышает число битов в исходных множителях.
Число битов целого числа – это свойство его типа, т.е. беззнаковый символьный тип имеет длину 8 битов, беззнаковое короткое целое имеет длину 16 битов, и т.д. К счастью, можно выполнять небольшую обработку типов при помощи шаблонов, чтобы найти правильные размеры в битах и типы для результатов умножения.
Для каждого типа, используемого в качестве базового класса B (иначе называемого малым типом) нужно найти соответствующий тип с вдвое( два раза) большим числом битов, который можно использовать для результата (иначе называемый большим типом).
Малый тип | Большой тип |
знаковый символьный | знаковый короткий целый |
беззнаковый символьный | беззнаковый короткий целый |
знаковый короткий целый | знаковый целый |
беззнаковый короткий целый | беззнаковый целый |
знаковый целый | знаковый длинный целый |
беззнаковый целый | беззнаковый длинный целый |
Было создано несколько закрытых шаблонных структур, дающих необходимую информацию во время компиляции:
template<>
struct promote_type<signed char>
{
typedef signed short type;
};
template<>
struct promote_type<unsigned char>
{
typedef unsigned short type;
};
template<>
struct promote_type<signed short>
{
typedef signed int type;
};
template<>
struct promote_type<unsigned short>
{
typedef unsigned int type;
};
template<>
struct promote_type<signed int>
{
typedef signed long long type;
};
template<>
struct promote_type<unsigned int>
{
typedef unsigned long long type;
};
Больший результат используется только временно. Установка десятичной точки после умножения выполняется при помощи сдвига результата вправо точно на F битов. После сдвига результат приводится обратно к исходному типу. И здесь нужно быть внимательным, так как умножение может вызывать переполнение примерно таким же образом, что и для целочисленного умножения.
Деление аналогично в том, что делимое с битами делится при помощи делителя с битами для получения результата с битами.
Сдвиг влево и сдвиг вправо
Оператор сдвига влево << и оператор сдвига вправо >> не определены для типов с плавающей точкой. Однако fixed_point<B, I, F> определяет их, так как он может использоваться для эмуляции целочисленного типа, когда F установлен в 0. В этом случае fixed_point<B, I, F> ведет себя как целое число, но дает доступ к основным математическим функциям (то есть они полезны, если нужно вычислить квадратный корень целого числа).
fixed_point<B, I, F> & operator >>=(size_t shift)
{
value_ >>= shift;
return *this;
}
fixed_point<B, I, F> & operator <<=(size_t shift)
{
value_ <<= shift;
return *this;
}
Класс boost::shiftable автоматически реализует операторы operator >> и operator << в виде operator >>= и operator <<=.
В форме записи псевдокода автоматическое предоставление операторов выглядит примерно так:
boost::shiftable | |
operator >>=(n) |
|
operator >>(n): | tmp(*this); tmp >>= n; return tmp; |
operator <<=(n) |
|
operator <<(n): | tmp(*this); tmp <<= n; return tmp; |
Ввод и вывод
Для удобства были реализованы операторы потокового ввода и вывода. Для реализации этих операторов было использовано преобразование в/из double.
template<typename S, typename B, unsigned char I, unsigned char F>
S & operator>>(S & s, fpml::fixed_point<B, I, F> & v)
{
double value=0.;
s >> value;
if (s)
v = value;
return s;
}
Поток ввода S – это параметр шаблона. Это позволяет использовать почти любой поток, будь то файловый или строковый поток, будь то поток символов в кодировке ANSI (Американский национальный институт стандартов) или поток символов расширенной формы.
template<typename S, typename B, unsigned char I, unsigned char F>
S & operator<<(S & s, fpml::fixed_point<B, I, F> const& v)
{
double value = v;
s << value;
return s;
}
Использование параметра шаблона S для потока вывода позволяет использовать любой поток, независимо от того, файловый это или строковый поток, или это поток символов в кодировке ANSI, или поток символов расширенной формы.
Потоковые операторы передают полномочия типу double. Нет особой выгоды в реализации этих операторов с нуля для типа fixed_point<B, I, F>. Ввод и вывод по определению медленный, поэтому допустимо применять математику с плавающей точкой в этих случаях.