Класс с фиксированной точкой - Создание и преобразование

ОГЛАВЛЕНИЕ

Создание и преобразование

Если нужно использовать объект fixed_point<B, I, F>, его сначала нужно создать. Класс предоставляет набор конструкторов. Имеется конструктор по умолчанию без параметров:

fixed_point()

{ }

Как и для встроенных типов, отсутствует инициализация. После выполнения этого конструктора значение неопределенное.

Имеется конструктор, допускающий создание из целых значений. Он реализован в виде шаблона:

template<typename T> fixed_point(T value) : value_((B)value << F)

{

    BOOST_CONCEPT_ASSERT((boost::Integer<T>));

}

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

Имеется конструктор, допускающий создание из логического значения:

fixed_point(bool value) : value_((B)(value * power2<F>::value))

{ }

Объект fixed_point<B, I, F>, созданный из ложь, имеет значение 0.0, а объект fixed_point<B, I, F>, созданный из истина, имеет значение 1.0.

Имеется ряд конструкторов, допускающих создание из значений с плавающей точкой:

fixed_point(float value) : value_((B)(value * power2<F>::value))

{ }

 

fixed_point(double value) : value_((B)(value * power2<F>::value))

{ }

 

fixed_point(long double value) : value_((B)(value * power2<F>::value))

{ }

Эти конструкторы принимают значение с плавающей точкой типа float, double или long double и преобразуют его в тип(у) fixed_point<B, I, F>. Преобразование выполняется путем умножения с подходящим показателем степени 2 и приведения результата к базовому типу B. Подходящий показатель степени 2 определяется по числу битов F дробной части. Это соответствует операции сдвига, выполняемой целочисленными конструкторами.

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

fixed_point<int, 16> a(0);

fixed_point<int, 16> b = -1.5;

Наконец, реализуется конструктор копирования:

fixed_point(fixed_point<B, I, F> const& rhs) : value_(rhs.value_)

{ }

Конструктор копирования просто копирует содержимое члена класса fixed_point<B, I, F>::value_.

Собственно, этот конструктор не обязателен, так как компилятор автоматически создает похожий конструктор копирования. Но лучше задать его в явном виде.

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

template<typename T> operator T() const

{

    BOOST_CONCEPT_ASSERT((boost::Integer<T>));

    return value_ >> F;

}

 

operator float() const

{

    return (float)value_ / power2<F>::value;

}

 

operator double() const

{

    return (double)value_ / power2<F>::value;

}

 

operator long double() const

{

    return (long double)value_ / power2<F>::value;

}

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

Несколько замечаний.

К сожалению, C++ не задает точное поведение операторов сдвига, но оставляет его реализацию определенной. Любая реализация может выполнять арифметический сдвиг (правильная обработка знакового бита для отрицательных чисел) или логический сдвиг (отсутствие особой обработки самого левого бита). При этом есть вероятность, что код не будет работать так, как предполагалось. Но опробованные реализации (Visual Studio 2005, Visual Studio 2008) работают правильно: они выполняют арифметический сдвиг для чисел со знаком и выполняют логический сдвиг для чисел без знака.

Преобразования с плавающей точкой должны вычислять 2 в степени F. Можно было бы использовать функцию времени выполнения pow, но нет смысла откладывать на время выполнения вычисления, которые столь же успешно можно выполнить во время компиляции. К сожалению, это не так просто. Для вычисления во время компиляции был использован метод метапрограммирования шаблона:

template<int F> struct power2

{
    static const long long value = 2 * power2<P-1,T>::value;
};

 

template <> struct power2<0>

{
    static const long long value = 1;
};

Шаблон power2 работает посредством рекурсии шаблона. Например, если F == 2, выполняются следующие шаги:

  1. создается экземпляр power2<2>, power2<2>::value устанавливается 2 * power2<1>::value. Так как power2<1>::value еще неизвестно, компилятор должен создать экземпляр power2<1>.
  2. создается экземпляр power2<1>, power2<1>::value устанавливается в 2 * power2<0>::value. Так как power2<0>::value еще неизвестно, компилятор должен создать экземпляр power2<0>.
  3. создается экземпляр power2<0>, power2<0>::value равно 1. Теперь рекурсия может перемотать весь путь обратно, эффективно вычислив 2 * 2 * 1, являющееся 2^2, равное 4.