Почему std :: pow (double, int) был удален из C ++ 11? - PullRequest
63 голосов
/ 12 апреля 2011

Рассматривая Эффективный способ вычисления 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, и явное замечание, что в случае смеси типов параметров, интегральных и двойных, что pow(double, double) перегрузка должна быть выбрана.

Является ли это только разъяснением предыдущего намерения, были ли они неправильно добавлены в C ++ 98, были ли они фактически удалены в C ++ 11 или что-то еще?

Очевидно, что версия pow(double, int) предоставляет хорошую возможность для оптимизации, поэтому кажется странным, что они будут удалены. Будет ли компилятор соответствовать стандартам для обеспечения такой оптимизированной перегрузки?

1 Ответ

82 голосов
/ 12 апреля 2011
double pow(double, int);

не был удален из спецификации. Это было просто перефразировано. Сейчас он живет в [c.math] / p11. Как это вычисляется - деталь реализации. Единственная подпись C ++ 03, которая изменилась:

float pow(float, int);

Это теперь возвращает double:

double pow(float, int);

И это изменение было сделано для совместимости с Си.

Разъяснение

26,8 [cmath] / p11 говорит:

Кроме того, должны быть дополнительные перегрузки, достаточные для обеспечения:

  1. Если какой-либо аргумент, соответствующий параметру double, имеет тип long double, тогда все аргументы, соответствующие двойные параметры эффективно приводятся к длинному двойному.

  2. В противном случае, если какой-либо аргумент соответствует двойному параметру имеет тип double или целочисленный тип, тогда все аргументы, соответствующие двойные параметры эффективно приводятся удвоить.

  3. В противном случае все аргументы, соответствующие двойным параметрам эффективно разыгрывается на плаву.

Этот параграф подразумевает целый ряд перегрузок, включая:

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) на отдельный алгоритм, большинство разработчиков, о которых я знаю, отказались от этой стратегии, за возможным исключением проверки очень малых интегральных показателей. И в этом случае, как правило, выгодно помещать эту проверку в реализацию с показателем с плавающей запятой, чтобы получить максимальную отдачу от вашей оптимизации.

...