В этом примере мы покажем несколько реализаций функцииJahnke и Emden Lambda, каждая из которых немного сложнее предыдущей.
Функция Янке-Эмдена Ламбда определяется уравнением:
JahnkeEmden(v, z) = Γ(v+1) * Jv(z) / (z/2)v
Если бы мы реализовали это с двойной точностью с помощью Boost. Средства математики для функции Гаммы и Бесселя выглядят так:
double JEL1(double v, double z)
{
return boost::math::tgamma(v + 1) * boost::math::cyl_bessel_j(v, z) / std::pow(z / 2, v);
}
Назвать эту функцию:
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10);
std::cout << JEL1(2.5, 0.5) << std::endl;
Производительность:
9.822663964796047e-001
Теперь давайте снова реализуем функцию, но на этот раз с помощью многоточного типа<cpp_dec_float_50
>в качестве аргументного типа:
boost::multiprecision::cpp_dec_float_50
JEL2(boost::multiprecision::cpp_dec_float_50 v, boost::multiprecision::cpp_dec_float_50 z)
{
return boost::math::tgamma(v + 1) * boost::math::cyl_bessel_j(v, z) / boost::multiprecision::pow(z / 2, v);
}
Реализация почти такая же, как и раньше, но с одним ключевым отличием - мы больше не можем называть<std::pow
>, вместо этого мы должны называть версию внутри<boost::multiprecision
>пространства имен. На самом деле, мы могли бы опустить префикс пространства имен при вызове<pow
>, поскольку правильная перегрузка была бы найдена черезаргументированный поискв любом случае.
Обратите внимание, что первый аргумент<pow
>вместе с аргументом<tgamma
>в приведенном выше коде на самом деле являются шаблонами выражения. Эти<pow
>и<tgamma
>функции прекрасно справятся с этими аргументами.
Вот пример того, как функция может быть названа:
std::cout << std::scientific << std::setprecision(std::numeric_limits<cpp_dec_float_50>::digits10);
std::cout << JEL2(cpp_dec_float_50(2.5), cpp_dec_float_50(0.5)) << std::endl;
Какие выходы:
9.82266396479604757017335009796882833995903762577173e-01
Теперь, когда мы увидели некоторые примеры без шаблонов, давайте повторим код снова, но на этот раз в качестве шаблона, который можно назвать либо со встроенным типом<float
>,<double
>и т. Д.
template <class Float>
Float JEL3(Float v, Float z)
{
using std::pow;
return boost::math::tgamma(v + 1) * boost::math::cyl_bessel_j(v, z) / pow(z / 2, v);
}
Код снова почти такой же, как и раньше, но призыв к<pow
>снова изменился. Нам нужен призыв к решению либо<std::pow
>(когда аргумент является встроенным типом), либо<boost::multiprecision::pow
>(когда аргумент является многоточным типом). Мы делаем это, делая вызов неквалифицированным, чтобы версии<pow
>, определенные в том же пространстве имен, что и тип<Float
>, были найдены с помощью поиска, зависящего от аргументов, в то время как директива<using
std::pow
>делает стандартные версии библиотеки видимыми для встроенных типов с плавающей запятой.
Назовем функцию с аргументами<double
>и многоточностью:
std::cout << std::scientific << std::setprecision(std::numeric_limits<double>::digits10);
std::cout << JEL3(2.5, 0.5) << std::endl;
std::cout << std::scientific << std::setprecision(std::numeric_limits<cpp_dec_float_50>::digits10);
std::cout << JEL3(cpp_dec_float_50(2.5), cpp_dec_float_50(0.5)) << std::endl;
Какие выходы:
9.822663964796047e-001
9.82266396479604757017335009796882833995903762577173e-01
К сожалению, есть проблема с этой версией: если бы мы назвали ее так:
boost::multiprecision::cpp_dec_float_50 v(2), z(0.5);
JEL3(v + 0.5, z);
Тогда мы получим длинное и непостижимое сообщение об ошибке от компилятора: проблема здесь в том, что первый аргумент к<JEL3
>не тип числа, а шаблон выражения. Очевидно, мы могли бы добавить тип-каст, чтобы решить проблему:
JEL(cpp_dec_float_50(v + 0.5), z);
Однако, если мы хотим, чтобы функция JEL была действительно многоразовой, то может быть предпочтительным лучшее решение. Для этого мы можем заимствовать код у Boost. Математика, которая вычисляет тип возврата функций смешанного аргумента, вот как теперь выглядит новый код:
template <class Float1, class Float2>
typename boost::math::tools::promote_args<Float1, Float2>::type
JEL4(Float1 v, Float2 z)
{
using std::pow;
return boost::math::tgamma(v + 1) * boost::math::cyl_bessel_j(v, z) / pow(z / 2, v);
}
Как вы можете видеть, два аргумента для функции теперь являются отдельными типами шаблонов, а тип возврата вычисляется с использованием метафункции<promote_args
>от Boost. Математика.
Теперь мы можем позвонить:
std::cout << std::scientific << std::setprecision(std::numeric_limits<cpp_dec_float_100>::digits10);
std::cout << JEL4(cpp_dec_float_100(2) + 0.5, cpp_dec_float_100(0.5)) << std::endl;
И получить 100 цифр вывода:
9.8226639647960475701733500979688283399590376257717309069410413822165082248153638454147004236848917775e-01
В качестве бонуса мы теперь можем называть функцию не только шаблонами выражений, но и другими смешанными типами: например<float
>и<double
>или<int
>и<double
>, и в каждом случае будет вычисляться правильный тип возврата.
Обратите внимание, что хотя в этом случае нам не нужно было менять тело функции, в общем случае любая функция, подобная этой, которая создает локальные переменные внутри, должна была бы использовать<promote_args
>для определения типа этих переменных, например:
template <class Float1, class Float2>
typename boost::math::tools::promote_args<Float1, Float2>::type
JEL5(Float1 v, Float2 z)
{
using std::pow;
typedef typename boost::math::tools::promote_args<Float1, Float2>::type variable_type;
variable_type t = pow(z / 2, v);
return boost::math::tgamma(v + 1) * boost::math::cyl_bessel_j(v, z) / t;
}