Изучая Эффективный способ вычисления p^q (возведение в степень), где q - целое число, и рассматривая C++98 и C Стандарты ++11 Я заметил, что, по-видимому, перегрузка std::pow(double, int) была удалена в C++11.
В C++98 26.5/6 он имеет подпись double pow(double, int);.
В C++11 26.8 все, что я мог найти, это перегрузки, принимающие пару float, double или long double, и явное примечание о том, что в случае смеси типов параметров integer&double следует выбрать перегрузку pow(double, double).
Является ли это просто разъяснением предыдущего намерения, были ли они неправильно добавлены в C++98, действительно ли они были удалены в C++11 или что-то еще?
Очевидно, что версия pow(double, int) предоставляет хорошие возможности для оптимизации, поэтому кажется странным, что они будут удалены. Будет ли компилятор по-прежнему соответствовать стандартам для обеспечения такой оптимизированной перегрузки?
Ответ 1 · рейтинг 83 · Howard Hinnant
double pow(double, int);
не был удален из спецификации. Его просто переформулировали. Теперь он находится в [c.math]/p11. Как это вычисляется, является деталью реализации. Единственная измененная сигнатура C++03:
float pow(float, int);
Теперь это возвращает двойной:
double pow(float, int);
И это изменение было сделано для совместимости с C.
Пояснение:
26.8 [cmath] / p11 говорит:
При этом должны быть предусмотрены дополнительные перегрузки, достаточные для обеспечения:
Если какой-либо аргумент, соответствующий параметру double, имеет тип long double, то все аргументы, соответствующие параметрам double, эффективно преобразуются в тип long double.
В противном случае, если какой-либо аргумент, соответствующий параметру double, имеет тип double или целочисленный тип, то все аргументы, соответствующие параметрам double, фактически преобразуются в тип double.
В противном случае все аргументы, соответствующие параметрам типа double, фактически преобразуются в число с плавающей запятой.
Этот пункт подразумевает целую массу перегрузок, среди которых:
double pow(double, int);
double pow(double, unsigned);
double pow(double, unsigned long long);
и Т. Д.
Это могут быть фактические перегрузки или они могут быть реализованы с помощью ограниченных шаблонов. Я лично реализовал его в обоих направлениях и решительно поддерживаю ограниченную реализацию шаблона.
Второе обновление для решения проблем с оптимизацией:
Реализация позволяет оптимизировать любую перегрузку. Но помните, что оптимизация должна быть только этим. Оптимизированная версия должна возвращать тот же ответ. Опыт разработчиков таких функций, как pow, заключается в том, что к тому времени, когда вы беретесь за обеспечение того, чтобы ваша реализация, использующая целочисленный показатель степени, давала тот же ответ, что и реализация, использующая показатель степени с плавающей запятой, "оптимизация" часто происходит медленнее.
В качестве демонстрации следующая программа выводит pow(.1, 20) дважды, один раз с использованием std::pow, а второй раз с использованием "оптимизированного" алгоритма, использующего целочисленный показатель степени:
#include <cmath>
#include <iostream>
#include <iomanip>
int main()
{
std::cout << std::setprecision(17) << std::pow(.1, 20) << '\n';
double x = .1;
double x2 = x * x;
double x4 = x2 * x2;
double x8 = x4 * x4;
double x16 = x8 * x8;
double x20 = x16 * x4;
std::cout << x20 << '\n';
}
В моей системе это распечатывает:
1.0000000000000011e-20
1.0000000000000022e-20
Или в шестнадцатеричной записи:
0x1.79ca10c92422bp-67
0x1.79ca10c924232p-67
И да, разработчики pow действительно беспокоятся обо всех этих битах на нижнем конце.
Таким образом, несмотря на то, что существует свобода перетасовать pow(double, int) в отдельный алгоритм, большинство разработчиков, которых я знаю, отказались от этой стратегии, за исключением, возможно, проверки очень малых целочисленных показателей. И в этом случае обычно выгодно поместить эту проверку в реализацию с экспонентой с плавающей запятой, чтобы получить максимальную отдачу от ваших затрат на оптимизацию.