Почему std:: pow (double, int) удален из С++ 11?

Изучая Эффективный способ вычисления p ^ q (экспоненциальность), где q - целое число и обзор стандартов С++ 98 и С++ 11 я заметил, что, по-видимому, перегрузка std::pow(double, int) была удалена в С++ 11.

В С++ 98 26.5/6 он имеет подпись double pow(double, int);.

В С++ 11 26.8 все, что я мог найти, это перегрузки, берущие пару float, double или long double, и явное замечание о том, что в случае комбинации типов параметров integer & double, это следует выбрать перегрузку pow(double, double).

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

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

Ответ 1

double pow(double, int);

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

float pow(float, int);

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

double pow(float, int);

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

Разъяснение

26.8 [cmath]/p11 говорит:

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

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

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

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

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

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