Чтобы быть как можно более общим, библиотека использует класс для вычисления всех необходимых функций, округленных вверх или вниз. Этот класс является первым параметром политики, это также тип, называемый округлениемв определении политики интервала.
По умолчанию этоинтервал_lib::rounded_math. Классинтервал_lib::rounded_mathуже специализируется на стандартных плавающих типахплавающих,двойныхидлинных двойных. Таким образом, если базовый тип интервалов не является одним из них, хорошим решением будет обеспечить специализацию этого класса. Но если по умолчанию специализацияrounded_mathдляfloat,doubleилиlong double- это не то, что вы ищете, или вы не хотите специализироватьсяinterval_lib::rounded_math(скажем, потому, что вы предпочитаете работать в своем собственном пространстве имен) вы также можете определить свою собственную политику округления и передать ее непосредственноinterval_lib::policies.
Requirements
Вот что должен предоставить класс. Домены пишутся рядом с соответствующими функциями (как видите, функции не должны беспокоиться о недействительных значениях, но они должны обрабатывать бесконечные аргументы).
/* Rounding requirements */
struct rounding {
// default constructor, destructor
rounding();
~rounding();
// mathematical operations
T add_down(T, T); // [-∞;+∞][-∞;+∞]
T add_up (T, T); // [-∞;+∞][-∞;+∞]
T sub_down(T, T); // [-∞;+∞][-∞;+∞]
T sub_up (T, T); // [-∞;+∞][-∞;+∞]
T mul_down(T, T); // [-∞;+∞][-∞;+∞]
T mul_up (T, T); // [-∞;+∞][-∞;+∞]
T div_down(T, T); // [-∞;+∞]([-∞;+∞]-{0})
T div_up (T, T); // [-∞;+∞]([-∞;+∞]-{0})
T sqrt_down(T); // ]0;+∞]
T sqrt_up (T); // ]0;+∞]
T exp_down(T); // [-∞;+∞]
T exp_up (T); // [-∞;+∞]
T log_down(T); // ]0;+∞]
T log_up (T); // ]0;+∞]
T cos_down(T); // [0;2π]
T cos_up (T); // [0;2π]
T tan_down(T); // ]-π/2;π/2[
T tan_up (T); // ]-π/2;π/2[
T asin_down(T); // [-1;1]
T asin_up (T); // [-1;1]
T acos_down(T); // [-1;1]
T acos_up (T); // [-1;1]
T atan_down(T); // [-∞;+∞]
T atan_up (T); // [-∞;+∞]
T sinh_down(T); // [-∞;+∞]
T sinh_up (T); // [-∞;+∞]
T cosh_down(T); // [-∞;+∞]
T cosh_up (T); // [-∞;+∞]
T tanh_down(T); // [-∞;+∞]
T tanh_up (T); // [-∞;+∞]
T asinh_down(T); // [-∞;+∞]
T asinh_up (T); // [-∞;+∞]
T acosh_down(T); // [1;+∞]
T acosh_up (T); // [1;+∞]
T atanh_down(T); // [-1;1]
T atanh_up (T); // [-1;1]
T median(T, T); // [-∞;+∞][-∞;+∞]
T int_down(T); // [-∞;+∞]
T int_up (T); // [-∞;+∞]
// conversion functions
T conv_down(U);
T conv_up (U);
// unprotected rounding class
typedef ... unprotected_rounding;
};
Конструктор и деструктор класса округления имеют очень важное семантическое требование: они отвечают за настройку и сброс режимов округления вычислений на Т. Например, если T является стандартным типом с плавающей точкой и вычисление с плавающей точкой выполняется в соответствии со стандартом IEEE 754, конструктор может сохранить текущее состояние округления, каждая_up(resp._down) функция округлит (resp. down), и деструктор восстановит сохраненное состояние округления. Действительно, это поведение политики округления по умолчанию.
Значение всех математических функций доatanh_upясно: каждая функция возвращает число, представленное в типеT, который является нижней границей (для_down) или верхней границей (для_up) на истинном математическом результате соответствующей функции. Функциямедианавычисляет среднее из двух аргументов, округленных до ближайшего репрезентабельного числа. Функцииint_downиint_upвычисляют ближайшее целое число меньше или больше их аргумента. Наконец,conv_downиconv_upотвечают за преобразование значений других типов в базовый тип числа: первый должен округлять значение, а второй должен округлять его.
Типunprotected_roundingпозволяет удалить все элементы управления. По причинам, по которым можно это сделать, см. ниже пункто защите.
Overview of the provided classes
Предусмотрено множество классов. Занятия организованы по уровням. Внизу находится классrounding_control. На следующем уровне идутrounded_arith_exact,rounded_arith_stdиrounded_arith_opp. Далее следуютrounded_transc_dummy,rounded_transc_exact,rounded_transc_stdиrounded_transc_opp. И, наконец,save_stateиsave_state_nothing. Каждый из этих классов предоставляет набор членов, которые необходимы классам следующего уровня. Например, классrounded_transc_...нуждается в членахrounded_arith_...Класс.
Когда они существуют в двух версиях_stdи_opp, первый каждый раз переключает режим округления, а второй пытается держать его ориентированным на плюс бесконечность. Основная цель версии_opp— ускорить вычисления с помощью «противоположного трюка» (см. примечанияк производительности). Эта версия требует, чтобы режим округления был вверх, прежде чем вводить какие-либо вычислительные функции класса. Это гарантирует, что режим округления будет по-прежнему вверх при выходе функций.
Обратите внимание, что это действительно очень плохая идея, чтобы смешать_oppверсия с_std, поскольку они не имеют совместимых свойств.
Существует третья версия под названием_exact, которая вычисляет функции без изменения режима округления. Это точная версия, потому что она предназначена для базового типа, который дает точные результаты.
Последняя версия_dummy. Он не выполняет никаких вычислений, но все же дает совместимые результаты.
Обратите внимание, что можно использовать «точную» версию для неточного базового типа, напримерfloatилиdouble. В этом случае свойство включения больше не гарантируется, но это может быть полезно для ускорения вычислений, когда свойство включения не требуется строго. Например, в компьютерной графике небольшая ошибка из-за округления с плавающей запятой допустима до тех пор, пока существует приблизительная версия свойства включения.
Вот что определяет каждый класс. Позже, когда они будут описаны более подробно, эти члены не будут повторяться. Пожалуйста, вернитесь сюда, чтобы увидеть их. Наследование также используется, чтобы избежать повторений.
template <class T>
struct rounding_control
{
typedef ... rounding_mode;
void set_rounding_mode(rounding_mode);
void get_rounding_mode(rounding_mode&);
void downward ();
void upward ();
void to_nearest();
T to_int(T);
T force_rounding(T);
};
template <class T, class Rounding>
struct rounded_arith_... : Rounding
{
void init();
T add_down(T, T);
T add_up (T, T);
T sub_down(T, T);
T sub_up (T, T);
T mul_down(T, T);
T mul_up (T, T);
T div_down(T, T);
T div_up (T, T);
T sqrt_down(T);
T sqrt_up (T);
T median(T, T);
T int_down(T);
T int_up (T);
};
template <class T, class Rounding>
struct rounded_transc_... : Rounding
{
T exp_down(T);
T exp_up (T);
T log_down(T);
T log_up (T);
T cos_down(T);
T cos_up (T);
T tan_down(T);
T tan_up (T);
T asin_down(T);
T asin_up (T);
T acos_down(T);
T acos_up (T);
T atan_down(T);
T atan_up (T);
T sinh_down(T);
T sinh_up (T);
T cosh_down(T);
T cosh_up (T);
T tanh_down(T);
T tanh_up (T);
T asinh_down(T);
T asinh_up (T);
T acosh_down(T);
T acosh_up (T);
T atanh_down(T);
T atanh_up (T);
};
template <class Rounding>
struct save_state_... : Rounding
{
save_state_...();
~save_state_...();
typedef ... unprotected_rounding;
};
Теперь мы описываем каждый класс в том порядке, в котором они появляются в определении политики округления (этот самый внешний и самый внутренний порядок является обратным порядком из синопсиса).
Protection control
Защита относится к тому факту, что интервальные операции будут окружены контролем режима округления. Незащита класса означает удаление всех элементов управления округлением. Каждая политика округления обеспечивает типunprotected_rounding. Требуемый типunprotected_roundingдает другой класс округления, который позволяет работать при вложении внутрь округления. Например, первые три строки ниже должны давать один и тот же результат (поскольку первая операция - это конструктор округления, а последняя - его деструктор, который заботится о настройке режимов округления); и последней линии разрешено иметь неопределенное поведение (поскольку ни один конструктор округления или деструктор никогда не называется).
T c; { rounding rnd; c = rnd.add_down(a, b); }
T c; { rounding rnd1; { rounding rnd2; c = rnd2.add_down(a, b); } }
T c; { rounding rnd1; { rounding::unprotected_rounding rnd2; c = rnd2.add_down(a, b); } }
T d; { rounding::unprotected_rounding rnd; d = rnd.add_down(a, b); }
Естественноокругление::unprotected_roundingможет быть простоокруглением. Но он может улучшить производительность, если это упрощенная версия с пустым конструктором и деструктором. Чтобы избежать неопределенного поведения, в библиотеке объект типаокругления::unprotected_roundingгарантированно создается только тогда, когда объект типаокругленияуже жив. См.примечания к исполнениюдля некоторых дополнительных деталей.
Библиотека поддержки определяет шаблон класса метапрограммированияunprotect, который принимает тип интервалаIи возвращает тип интервалаunprotect::type, где политика округления была незащищена. Некоторая информация о типах:интервал>::traits_type::roundingявляетсятем же типом, что иRounding, иunprotect>>::typeявляетсятем же типом, что иинтервал>.
State saving
Первый из нихSave_state. Этот класс отвечает за сохранение текущего режима округления и вызов init в своем конструкторе, а также за восстановление сохраненного режима округления в своем деструкторе. Этот класс также определяет типunprotected_rounding.
Если режим округления не требует сохранения состояния или инициализации,save_state_nothingможет использоваться вместоsave_state.
Transcendental functions
Классыrounded_transc_exact,rounded_transc_stdиrounded_transc_oppожидают, что пространство имен std обеспечит функции exp log cos tan acos asin atan cosh sinh tanh acosh asinh atanh. Для версий_stdи_oppвсе эти функции должны соблюдать текущий режим округления, фиксированный вызовом вниз или вверх.
Пожалуйста, обратите внимание:К сожалению, последнее случается редко. Именно по этой причине предоставляется классrounded_transc_dummy, который не зависит от функций из пространства имен std. Однако никакой магии нет. Функцииrounded_transc_dummyничего не вычисляют. Они возвращают только действительные значения. Например,cos_downвсегда возвращается. -1. Таким образом, мы проверяем свойство включения для реализации по умолчанию, даже если это не имеет строго никакого значения для пользователя. Для того, чтобы иметь полезные ценности, следует использовать другую политику, которая, скорее всего, приведет к нарушению свойства включения. Таким образом, мы гарантируем, что нарушение четко указано пользователю, который знает, против чего он выступает. Этот класс мог использоваться в качестве класса трансцендентальной округления по умолчанию, но было решено, что компиляция будет лучше провалиться из-за отсутствующих деклараций, а не преуспеть благодаря действительным, но непригодным функциям.
Basic arithmetic functions
Классыrounded_arith_stdиrounded_arith_oppожидают, что операторы + - * / и функцияstd::sqrtбудут соблюдать текущий режим округления.
Классrounded_arith_exactтребует определенияstd::floorиstd::ceil, поскольку он не может полагаться наto_int.
Rounding control
Функции, определенные каждым из предыдущих классов, не нуждались в объяснении. Например, поведениеadd_downзаключается в вычислении суммы двух чисел, округленных вниз. Дляrounding_controlситуация немного сложнее.
Основной функцией являетсяforce_rounding, которая возвращает свой аргумент правильно округленным в соответствии с текущим режимом округления, если он еще не был таковым. Эта функция необходима для обработки задержки округления. Действительно, в зависимости от способа выполнения вычислений промежуточные результаты могут быть внутренне сохранены в более точном формате, и это может привести к неправильной округлению. Таким образом, функция обеспечивает округление.Вотпример того, что происходит, когда округление не выполняется.
Функцияget_rounding_modeвозвращает текущий режим округления,set_rounding_modeвозвращает режим округления к предыдущему значению, возвращаемомуget_rounding_mode.вниз,вверхик_nearestустанавливает режим округления в одном из трех направлений. Этот режим округления должен быть глобальным для всех функций, использующих типT. Например, после вызовавниз,силовое округление (x+y), как ожидается, вернет сумму, округленную в сторону -∞.
Функцияto_intвычисляет ближайшее целое число соответственно текущему режиму округления.
Неспециализированная версияrounding_controlничего не делает. Функции для режима округления пусты, аto_intиforce_roundingявляются функциями идентификации. Константные функцииpi_возвращают подходящие целые числа (например,pi_upвозвращаетT(4)).
Шаблон классаrounding_controlпредназначен дляfloat,doubleиlong double, чтобы наилучшим образом использовать блок плавающей точки компьютера.
Template class rounded_math
Политика по умолчанию (он жеrounded_math) определяется следующим образом:
Этот пункт касается в основном производительности библиотеки с интервалами с использованием блока с плавающей запятой (FPU) компьютера. Рассмотрим в качестве примера суммуa,bиc,d. В результате [вниз[a+c,вверх[b+d]], гдевнизивверхуказывают на необходимый режим округления.
Rounding Mode Switch
Если FPU может использовать другой режим округления для каждой операции, проблем нет. Например, это относится к процессору Alpha: каждая инструкция с плавающей запятой может указывать другой режим округления. Однако стандарт IEEE-754 не требует такого поведения. Таким образом, большинство FPU предоставляют только некоторые инструкции по настройке режима округления для всех последующих операций. И вообще, эти инструкции нужно промыть трубопроводом ФПУ.
В этой ситуации время, необходимое для суммирования [a,b] и [c,d], намного хуже времени, необходимого для вычисленияa+bиc+d, поскольку два добавления не могут быть распараллелены. Следовательно, цель состоит в том, чтобы уменьшить количество переключателей режима округления.
Если эта библиотека не используется для обеспечения точных вычислений, а только для парной арифметики, решение довольно простое: не используйте округление. В этом случае выполнение суммы [a,bи [c,d] будет таким же быстрым, как вычислениеa+bиc+d. Все идеально.
Однако, если требуются точные вычисления, такое решение совершенно немыслимо. Значит, мы без гроша? Нет, все еще есть трюк. Действительно, внизa+c) = -up (-a-c), если унарный минус является точной операцией. Теперь можно рассчитать всю сумму в том же режиме округления. Как правило, стоимость переключения режима хуже, чем стоимость изменения знака.
Speeding up consecutive operations
Добавление интервала не является единственной операцией; большинство интервальных операций можно вычислить, установив направление округления FPU только один раз. Таким образом, операции политики округления с плавающей точкой предполагают, что направление правильно установлено. Это предположение обычно не верно в программе (пользователь и стандартная библиотека ожидают, что направление округления будет ближе), поэтому эти операции должны быть заключены в оболочку, которая устанавливает среду с плавающей точкой. Эта защита осуществляется конструктором и разрушителем политики округления.
Рассмотрим теперь случай двух последовательных интервальных добавлений:a,b+c,d+e,f. Созданный код должен выглядеть так:
init_rounding_mode(); // rounding object construction during the first addition
t1 = -(-a - c);
t2 = b + d;
restore_rounding_mode(); // rounding object destruction
init_rounding_mode(); // rounding object construction during the second addition
x = -(-t1 - e);
y = t2 + f;
restore_rounding_mode(); // rounding object destruction
// the result is the interval [x,y]
Между двумя операциями направление округления восстанавливается, а затем инициализируется снова. В идеале компиляторы должны быть в состоянии оптимизировать этот бесполезный код. Но, к сожалению, это не так, и это замедляет код на порядок. Чтобы избежать этого узкого места, пользователь может сказать интервальным операциям, что их больше не нужно защищать. Затем пользователь должен будет защитить интервальные вычисления. Компилятор сможет генерировать такой код:
init_rounding_mode(); // done by the user
x = -(-a - c - e);
y = b + d + f;
restore_rounding_mode(); // done by the user
Пользователю придется создать округлый объект. И пока этот объект жив, можно использовать незащищенные версии интервальных операций. Они выбираются с использованием интервального типа с определенной политикой округления. Если начальный тип интервала —I, тоI::traits_type::rounding— тип объекта округления, аinterval_lib::unprotect::type— тип незащищенного типа интервала.
Поскольку режим округления FPU изменяется в течение жизни объекта округления, любая арифметическая операция с плавающей точкой, не связанная с библиотекой интервалов, может привести к неожиданным результатам. Взаимно, используя незащищенную интервальную операцию, когда ни один округлый объект не является живым, мы получим интервалы, которые больше не гарантированы, чтобы содержать реальный результат.
Example
Вот пример схемы Хорнера для вычисления значения полинома. Переключатели режима округления отключены для всего вычисления.
// I is an interval class, the polynom is a simple array
template<class I>
I horner(const I& x, const I p[], int n) {
// save and initialize the rounding mode
typename I::traits_type::rounding rnd;
// define the unprotected version of the interval type
typedef typename boost::numeric::interval_lib::unprotect<I>::type R;
const R& a = x;
R y = p[n - 1];
for(int i = n - 2; i >= 0; i--) {
y = y * a + (const R&)(p[i]);
}
return y;
// restore the rounding mode with the destruction of rnd
}
Обратите внимание, что объект округления специально создан для защиты всех интервальных вычислений. Каждый интервал типа I преобразуется в интервал типа R перед любыми операциями. Если это преобразование не сделано, результат все равно правильный, но интерес всей этой оптимизации исчез. Когда это возможно, хорошо преобразовать вconst R&вместоR: действительно, функция уже может быть вызвана внутри блока без защиты, поэтому типыRиIбудут одинаковым интервалом, без необходимости преобразования.
Uninteresting remark
В начале было сказано, что процессоры Alpha могут использовать определенный режим округления для каждой операции. Однако из-за формата инструкции округление в сторону плюс бесконечности недоступно. Можно использовать только округление в сторону минус бесконечности. Таким образом, трюк с использованием смены знака становится необходимым, но нет необходимости сохранять и восстанавливать режим округления по обе стороны операции.
Extended Registers
Есть еще одна проблема, помимо стоимости переключателя режима округления. Некоторые FPU используют расширенные регистры (например, поплавковые вычисления будут выполняться с двойными регистрами или двойными вычислениями с длинными двойными регистрами). Следовательно, может возникнуть много проблем.
Первая обусловлена повышенной точностью мантисы. Закругление также выполняется с этой расширенной точностью. И, следовательно, мы все еще имеем понижениеa+b) = -up (-a-b) в расширенных регистрах. Но вернемся к стандартной точности, теперь у нас есть внизa+b<-upa-bвместо равенства. Решением может быть не использование этого метода. Но есть и другие проблемы, например, сравнение между числами.
Естественно, существует также проблема с расширенной точностью экспоненты. Чтобы проиллюстрировать эту проблему, пустьмбудет самым большим числом до +инф. Если вычислить 2*м,м, то ответ должен бытьм,инф. Но благодаря расширенным регистрам FPU сначала будет хранить [2m,2m], а затем преобразует его в [inf,inf] в конце исчисления (когда режим округления приближается к +inf). Поэтому ответ не более точный.
Есть только одно решение: заставить FPU преобразовывать расширенные значения обратно в стандартную точность после каждой операции. Некоторые FPU предоставляют инструкцию, способную выполнять это преобразование (например, процессоры PowerPC). Но для FPU, которые его не предоставляют (процессоры x86), единственным решением является записать значения в память и прочитать их обратно. Такая операция, очевидно, очень дорогая.
Some Examples
Вот несколько случаев:
если вам нужны точные вычисления сfloatилиdoubleтипами, используйте по умолчаниюrounded_math<T>;
для быстрых широких интервалов без какой-либо округления или точности, используйтеsave_state_nothing<rounded_transc_exact<T>
>;
Для точного типа (например, int или рационального с небольшой помощью для бесконечных и NaN значений) без поддержки трансцендентных функций решение может бытьsave_state_nothing<rounded_transc_dummy<T,
rounded_arith_exact<T> > >или непосредственноsave_state_nothing<rounded_arith_exact<T>
>.
Если это более сложный случай, чем предыдущие, лучше всего, вероятно, напрямую определить свою собственную политику.
Статья Rounding Policies раздела может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.