Класс с фиксированной точкой - Операторы

ОГЛАВЛЕНИЕ


Операторы

Разумеется, чтобы была возможность делать что-либо полезное с объектами 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>. Ввод и вывод по определению медленный, поэтому допустимо применять математику с плавающей точкой в этих случаях.