Многоточная библиотека предоставляетцелые,рациональныеиплавающие точкитипы в C++, которые имеют больший диапазон и точность, чем обычные встроенные типы C++. Многоточность может быть использована с широким выбором основных математических операций, элементарных трансцендентальных функций, а также функций в Boost. Математика. Многоточные типы также могут взаимодействовать со встроенными типами в C++, используя четко определенные правила преобразования. Это позволяет увеличить. Многоточность используется для всех видов математических расчетов, включающих целые, рациональные и плавающие типы точек, требующие расширенного диапазона и точности.
Многоточность состоит из общего интерфейса к математике больших чисел, а также выбора больших задних концов с поддержкой целых, рациональных и плавающих типов точек. Повышаю. Multiprecision предоставляет выбор бэк-эндов, предоставляемых вне стойки, включая интерфейсы для GMP, MPFR, MPIR, TomMath, а также собственную коллекцию бэк-эндов Boost, только для заголовков для целых чисел, рационалов и поплавков. Кроме того, пользовательские оконечности могут быть созданы и использованы с интерфейсом Multiprecision при условии, что реализация класса соответствует необходимымконцепциям..
В зависимости от типа числа точность может быть произвольно большой (ограниченной только доступной памятью), фиксированной во время компиляции (например, 50 или 100 десятичных цифр) или переменной, управляемой во время выполнения функциями-членами. Типы с поддержкой экспрессии-шаблоны для лучшей производительности, чем наивные пользовательские типы.
Многоточная библиотека состоит из двух частей:
- Передняя часть с поддержкой экспрессионного шаблона<
number
>, которая обрабатывает перегрузку оператора, оптимизацию оценки экспрессии и сокращение кода.
- Выбор бэкэндов, которые реализуют фактические арифметические операции и должны соответствовать только уменьшенным требованиям интерфейса интерфейса.
Разделение front-end и back-end позволяет использовать высококачественные, но ограниченные библиотеки лицензий, где это возможно, но предоставляет альтернативы лицензии Boost для пользователей, которые должны иметь портативную лицензию. То есть некоторые бэкэнды полагаются на 3-ю партийную библиотеку, но версия лицензии Boost всегда доступна (если немного медленнее).
Если вы просто хотите перейти к погоне и использовать полностью лицензированный тип числа, перейдите кcpp_intдля многоточных целых чисел,cpp_dec_floatдля многоточных типов с плавающей точкой иcpp_rationalдля рациональных типов.
Библиотека часто используется с помощью одного из предопределенных типдефов: например, если вы хотитепроизвольной точностицелочисленного типа с использованиемGMPв качестве базовой реализации, тогда вы можете использовать:
#include <boost/multiprecision/gmp.hpp>
boost::multiprecision::mpz_int myint;
В качестве альтернативы вы можете составить свой собственный многоточный тип, объединив<number
>с одним из предопределенных типов бэкэнда. Например, предположим, что вам нужен тип с плавающей точкой размером 300 десятичных цифр на основе библиотекиMPFR. В этом случае не существует заранее определенной типдеф с таким уровнем точности, поэтому вместо этого мы составляем свои собственные:
#include <boost/multiprecision/mpfr.hpp>
namespace mp = boost::multiprecision;
typedef mp::number<mp::mpfr_float_backend<300> > my_float;
my_float a, b, c;
Мы можем повторить приведенный выше пример, но с отключенными шаблонами выражения (для более быстрого времени компиляции, но более медленного времени выполнения), передав второй аргумент шаблона<number
>:
#include <boost/multiprecision/mpfr.hpp>
namespace mp = boost::multiprecision;
typedef mp::number<mp::mpfr_float_backend<300>, et_off> my_float;
my_float a, b, c;
Мы также можем смешивать арифметические операции между различными типами при условии однозначного неявного преобразования из одного типа в другой:
#include <boost/multiprecision/cpp_int.hpp>
namespace mp = boost::multiprecision;
mp::int128_t a(3), b(4);
mp::int512_t c(50), d;
d = c * a;
Допускаются также конверсии:
d = a;
d = a * b;
Однако конверсии, которые по своей сути являются убыточными, либо объявляются явными, либо вообще запрещены:
d = 3.14;
d = static_cast<mp::int512_t>(3.14);
Смешанная арифметика не сработает, если преобразование является двусмысленным или явным:
number<cpp_int_backend<>, et_off> a(2);
number<cpp_int_backend<>, et_on> b(3);
b = a * b;
b = a * 3.14;
На компиляторах, поддерживающих rvalue-ссылки, класс<number
>имеет возможность перемещения, если базовый бэкэнд.
Кроме того, неэкспрессионные шаблонные операторские перегрузки (см. ниже) имеют перегрузки, которые выглядят примерно так:
template <class B>
number<B, et_off> operator + (number<B, et_off>&& a, const number<B, et_off>& b)
{
return std::move(a += b);
}
Эти перегрузки оператора гарантируют, что многие выражения могут быть оценены без каких-либо временных изменений. Тем не менее, есть еще много простых выражений, таких как:
a = b * c;
Которые не заметно выигрывают от поддержки движения. Таким образом, оптимальная производительность обеспечивается как поддержкой движения, так и включенными шаблонами выражения.
Обратите внимание, что в то время как объекты, «перемещенные из» оставлены в нормальном состоянии, они имеют неопределенное значение, и единственными разрешенными операциями на них являются уничтожение или присвоение нового значения. Любая другая операция должна считаться ошибкой программирования, и все наши бэкэнды вызовут утверждение, если будет предпринята какая-либо другая операция. Такое поведение обеспечивает оптимальную производительность при построении на ходу (т.е. не требуется никакого распределения, мы просто берем на себя ответственность за внутреннее состояние существующего объекта), сохраняя при этом удобство использования в стандартных библиотечных контейнерах.
Класс<number
>с поддержкой экспрессионного шаблона: это означает, что вместо оператора умножения, который выглядит следующим образом:
template <class Backend>
number<Backend> operator * (const number<Backend>& a, const number<Backend>& b)
{
number<Backend> result(a);
result *= b;
return result;
}
Вместо этого оператор выглядит примерно так:
template <class Backend>
unmentionable-type operator * (const number<Backend>& a, const number<Backend>& b);
Когда «неумолимый» тип возврата представляет собой деталь реализации, которая вместо того, чтобы содержать результат умножения, содержит инструкции о том, как вычислить результат. По сути, это просто пара ссылок на аргументы функции, а также некоторая информация о времени компиляции, которая хранит то, что является операцией.
Большим преимуществом этого метода являетсяустранение временных: например, «наивная» реализация<operator*
>выше требует одного временного для вычисления результата, и, по крайней мере, другого для его возврата. Это правда, что иногда эти накладные расходы могут быть уменьшены с помощью семантики движения, но они не могут быть полностью устранены. Например, предположим, что мы оцениваем полином с помощью метода Хорнера, что-то вроде этого:
T a[7] = { };
y = (((((a[6] * x + a[5]) * x + a[4]) * x + a[3]) * x + a[2]) * x + a[1]) * x + a[0];
Если тип<T
>является<number
>, то это выражение оцениваетсябез создания единого временного значения. Напротив, если бы мы использовали оберткуmpfr_classC++ дляMPFR- тогда это выражение привело бы к не менее чем 11 временным (это верно, хотяmpfr_classдействительно использует шаблоны экспрессии, чтобы несколько уменьшить количество временных интервалов). Если бы мы использовали еще более простую обертку вокругMPFR, какmpreal, все было бы еще хуже, и не менее того, для этого простого выражения созданы 24 временных интервала (примечание - мы на самом деле измеряем количество выполненных выделений памяти, а не количество временных непосредственно, обратите внимание также, что оберткаmpf_class, которая будет поставляться с GMP-5.1, уменьшает количество временных интервалов почти до нуля). Обратите внимание, что если мы компилируем шаблоны выражений с отключенной поддержкой и поддержкой ссылок на значения, то на самом деле все еще нет потерянных выделений памяти, поскольку даже при создании временных интервалов их содержимое перемещается, а не копируется.
![[Important]](/img/important.png) |
Important |
Шаблоны экспрессии могут радикально переупорядочивать операции в выражении, например:
a = (b * c) * a;
Преобразуется в:
a * = c; a * = b;
Если это, вероятно, будет проблемой для конкретного приложения, то они должны быть отключены.
|
Эта библиотека также расширяет поддержку шаблонов выражения для стандартных функций библиотеки, таких как<abs
>или<sin
>с<number
>аргументами. Это означает, что такое выражение, как:
y = abs(x);
Его можно оценить без единого временного расчета. Даже такие выражения как:
y = sin(x);
Получите эту обработку, чтобы переменная «y» использовалась в качестве «рабочего хранилища» в реализации<sin
>, тем самым уменьшая количество временно используемых одним. Конечно, если вы напишете:
x = sin(x);
Тогда мы явно не можем использовать<x
>в качестве рабочего хранилища во время вычисления, поэтому в этом случае создается временная переменная.
Учитывая приведенные выше комментарии, вы можете быть прощены за то, что думаете, что шаблоны выражения являются своего рода универсальной панацеей: к сожалению, все подобные трюки имеют свои недостатки. Во-первых, библиотеки шаблонов выражений, подобные этой, как правило, медленнее компилируются, чем их более простые родственники, их также сложнее отлаживать (если вы действительно хотите перейти через наш код!) и полагаться на оптимизацию компилятора, чтобы обеспечить действительно хорошую производительность. Кроме того, поскольку тип возврата от выражений, включающих<number
>s, является «неумолимой деталью реализации», вы должны быть осторожны, чтобы отлить результат выражения к фактическому типу числа при передаче выражения к функции шаблона. Например, учитывая:
template <class T>
void my_proc(const T&);
Тогда звоните:
my_proc(a+b);
Весьма вероятно, что это приведет к неясным сообщениям об ошибках внутри тела<my_proc
>- поскольку мы передали ему тип шаблона выражения, а не тип числа. Скорее всего, нам нужно:
my_proc(my_number_type(a+b));
Сказав это, эти ситуации происходят не так часто или вообще не для нешаблонных функций. Кроме того, все функции в бусте. Математическая библиотека автоматически преобразует аргументы шаблона выражения в основной тип числа без необходимости что-либо делать.
mpfr_float_100 a(20), delta(0.125);
boost::math::gamma_p(a, a + delta);
Будет работать просто отлично, с аргументом шаблона выражения<a+delta
>, преобразующимся в<mpfr_float_100
>внутри Boost. Математическая библиотека.
Еще одна потенциальная ошибка, которая возможна только в C++11: вы никогда не должны хранить шаблон выражения, используя:
auto my_expression = a + b - c;
Если вы не уверены, что жизнь<a
>,<b
>и<c
>переживет жизнь<my_expression
>.
И, наконец, улучшения производительности из библиотеки шаблонов экспрессии, подобные этой, часто не так драматичны, как можно было бы предположить при сокращении количества временных интервалов. Например, если мы сравним эту библиотеку сmpfr_classиmpreal, причем все три используют базовуюMPFRбиблиотеку с точностью 50 десятичных цифр, то мы увидим следующие типичные результаты для полиномиального исполнения:
Table 1.1. Evaluation of Order 6 Polynomial.
Библиотека
|
Относительное время
|
Относительное количество выделений памяти
|
Номер |
1.0 (0.00957s) |
1.0 (всего 2996) |
mpfr_class |
1.1 (0.0102s) |
4.3 (12976 всего) |
mpreal |
1.6 (0.0151s) |
9.3 (27947 всего) |
Как видите, время выполнения увеличивается гораздо медленнее, чем количество выделений памяти. Для этого существует ряд причин:
- Стоимость умножения и деления с расширенной точностью настолько велика, что время, затраченное на это, затопляет все остальное.
- Стоимость умножения в месте (с использованием<
operator*=
>) имеет тенденцию быть больше, чем вне места<operator*
>(обычно<operator*=
>должен создать временное рабочее пространство для выполнения умножения, где как<operator*
>может использовать целевую переменную в качестве рабочего пространства). Так как шаблоны выражения осуществляют свою магию, преобразуя неуместных операторов в неуместных, мы обязательно принимаем этот хит. Тем не менее, трансформация более эффективна, чем создание дополнительной временной переменной, но не настолько, насколько можно было бы надеяться.
Наконец, обратите внимание, что<number
>принимает второй аргумент шаблона, который при установке на<et_off
>отключает все механизмы шаблона выражения. Результат намного быстрее компилируется, но медленнее во время выполнения.
Мы завершим этот раздел, предоставив еще несколько сравнений производительности между этими тремя библиотеками, опять же, все используютMPFRдля выполнения основной арифметики, и все работают с одинаковой точностью (50 десятичных цифр):
Table 1.2. Evaluation of Boost.Math's Bessel function test data
Библиотека
|
Относительное время
|
Относительное количество распределений памяти
|
mpfr_float_50 |
1.0 (5.78s) |
1.0 (1611963) |
number, et_off> (но с поддержкой ссылки на значение r) |
1.1 (6.29) |
2.64 (4260868) |
mpfr_class |
1.1 (6.28s) |
2.45 (3948316) |
mpreal |
1,65 (9,54) |
8.21 (13226029) |
Table 1.3. Evaluation of Boost.Math's Non-Central T distribution test data
Библиотека
|
Относительное время
|
Относительное количество распределений памяти
|
Номер |
1,0 (263s) |
1.0 (127710873) |
number, et_off> (но с поддержкой ссылки на значение r) |
1,0 (260) |
1.2 (156797871) |
mpfr_class |
1.1 (287) |
2.1 (268336640) |
mpreal |
1.5 (389) |
3.6 (466960653) |
Вышеуказанные результаты были получены при компиляции Win32 с Visual C++ 2010, всех оптимизаций на (/Ox), с MPFR 3.0 и MPIR 2.3.0.