Почему int pow(int base, int exponent) не находится в стандартных библиотеках c++?

Я чувствую, что я просто не могу найти его. Есть ли причина, по которой функция c++ pow не реализует функцию "power" ни для чего, кроме поплавков и двойников?

Я знаю, что реализация тривиальна, я просто чувствую, что делаю работу, которая должна быть в стандартной библиотеке. Надежная функция питания (т. е. обрабатывает переполнение каким-то последовательным, явным способом) писать неинтересно.

10 ответов


поскольку я не был тесно связан с создателями C или C++ в дни их создания (хотя я am довольно старый), ни часть комитетов ANSI / ISO, которые создали стандарты, это обязательно мнение с моей стороны. Хотелось бы думать, что это сообщил мнение, но, как скажет вам моя жена (часто и без особого поощрения), я был неправ раньше: -)

предположение, для чего это стоит, следует.

Я подозреваю, что причина, по которой оригинал (pre-ANSI) C не имел этой функции, заключается в том, что это было совершенно ненужно. Уже был отличный способ сделать целочисленные полномочия (с двойниками, а затем просто преобразовать обратно в целое число, давая вам возможность проверить переполнение и underflow integer перед преобразованием).

другое, что вы должны помнить, это то, что первоначальное намерение C было системы язык программирования и в любом случае, сомнительно, что плавающая точка желательна на этой арене. Поскольку первоначальным вариантом использования было кодирование UNIX, плавающая точка была бы почти бесполезной. BCPL, на котором был основан C, также не использовал полномочия (у него вообще не было плавающей точки, по памяти).

в стороне, интегральный оператор мощности, вероятно, был бы двоичным оператором, а не вызовом библиотеки. Вы не добавляете два целых числа с x = add (y, z) но с x = y + z - часть язык вместо библиотеки.

поскольку реализация интегральной мощности относительно тривиальна, почти наверняка разработчики языка будут лучше использовать свое время, предоставляя более полезные материалы (см. ниже комментарии к opportunity cost).

это также актуально для исходного C++. Поскольку первоначальная реализация была фактически просто переводчиком, который произвел код C, он перенес многие атрибуты C. его первоначальным намерением было C-с-классами, а не C-с-классами-плюс-немного-дополнительной-математики.

что касается того, почему он никогда не был добавлен в стандарты, вы должны помнить, что органы, устанавливающие стандарты, имеют конкретные руководящие принципы. Например, ANSI C было специально поручено кодифицировать существующую практику,не для создания нового языка. Иначе они могли сойти с ума и дать нам аду: -)

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

например, документ C99 rationale специально переносит два руководящих принципа C89, которые ограничивают то, что можно добавить:

  • держите язык маленьким и простым.
  • обеспечьте только один путь сделать деятельность.

рекомендации (не обязательно те конкретные ones) устанавливаются для отдельных рабочих групп и, следовательно, ограничивают комитеты C++ (и все другие группы ISO).

кроме того, органы, устанавливающие стандарты, понимают, что существует стоимостью (экономический термин, означающий то, что вы должны отказаться от принятого решения) для каждого решения, которое они принимают. Например, стоимость возможности покупки этого $ 10,000 uber-gaming машина-это сердечные отношения (или, возможно,все отношения) с другой половиной в течение примерно шести месяцев.

Эрик Ганнерсон хорошо объясняет это своим -100 очков объяснение что касается того, почему вещи не всегда добавляются в продукты Microsoft - в основном функция начинает 100 точек в отверстии, поэтому она должна добавить совсем немного значения, чтобы даже считаться.

другими словами, вы бы предпочли иметь интегральный оператор мощности (который, честно говоря, любой код обезьяна может взбить за десять минут) или многопоточность добавлена к стандарту? Что касается меня, я бы предпочел иметь последнее и не возиться с различными реализациями под UNIX и Windows.

Я хотел бы также увидеть тысячи и тысячи коллекций стандартной библиотеки (хэши, btrees, красно-черные деревья, словарь, произвольные карты и т. д.), Но, как говорится в обосновании:

стандартный договор между исполнителем и программист.

и количество исполнителей в органах стандартов намного превышает количество программистов (или, по крайней мере, тех программистов, которые не понимают стоимости возможностей). Если бы все это было добавлено, следующий стандартный C++ был бы c++215x и, вероятно, был бы полностью реализован разработчиками компиляторов через триста лет после этого.


для любого интегрального типа с фиксированной шириной почти все возможные входные пары переполняют тип. Какой смысл стандартизировать функцию, которая не дает полезного результата для подавляющего большинства ее возможных входов?

вам в значительной степени нужно иметь большой целочисленный тип, чтобы сделать функцию полезной, и большинство больших целочисленных библиотек предоставляют функцию.


Edit: в комментарии к вопросу static_rtti пишет " Большинство входы вызывают его переполнение? То же самое верно для exp и double pow, я не вижу, чтобы кто-то жаловался.- Это неверно.

оставим в стороне exp, потому что это не относится к делу (хотя на самом деле это сделало бы мой случай сильнее), и сосредоточьтесь на double pow(double x, double y). Для какой части пар (x,y) эта функция делает что-то полезное (т. е. не просто переполнение или утечка)?

Я на самом деле собираюсь сосредоточиться только на небольшой части входных пар, для которых pow имеет смысл, потому что этого будет достаточно, чтобы доказать мою точку зрения: если x положительно и |y| pow не переполняет или underflow. Это включает почти четверть всех пар с плавающей запятой (ровно половина чисел с плавающей запятой не-NaN положительны, и только менее половины чисел с плавающей запятой не-NaN имеют величину меньше 1). Очевидно, есть большое других входных пар, для которых pow дает полезные результаты, но мы выяснили, что это на как минимум четверть всех входных данных.

теперь давайте посмотрим на целочисленную степенную функцию фиксированной ширины (т. е. не-bignum). Для какой части входов он не просто переполняется? Чтобы максимизировать количество значимых входных пар, база должна быть подписана, а показатель без знака. Предположим, что база и показатель равны n битов шириной. Мы можем легко получить оценку части входных данных, которые имеют смысл:

  • если показатель 0 или 1, то любая база многозначительный.
  • если показатель равен 2 или больше, то никакая база больше 2^(n/2) не дает значимого результата.

таким образом, из 2^(2n) входных пар менее 2^(n+1) + 2^(3n/2) дают значимые результаты. Если мы посмотрим на то, что, вероятно, является наиболее распространенным использованием, 32-разрядные целые числа, это означает, что что-то порядка 1/1000 процента входных пар не просто переполняется.


потому что нет способа представить все целочисленные степени в int в любом случае:

>>> print 2**-4
0.0625

это действительно интересный вопрос. Один аргумент, который я не нашел в обсуждении, - это простое отсутствие очевидных возвращаемых значений для Аргументов. Давайте посчитаем способы hypthetical int pow_int(int, int) функция может потерпеть неудачу.

  1. переполнения
  2. результат не определен pow_int(0,0)
  3. результат не может быть представлен pow_int(2,-1)

функция имеет по крайней мере 2 режима отказа. Целые числа не могут представлять эти значения, поведение функции в этих случаях необходимо будет определить стандарт - и программисты должны будут знать, как именно функция обрабатывает эти случаи.

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


короткий ответ:

спецификация pow(x, n) где n - натуральное число часто полезно для срок выполнения. Но стандартная библиотека общая pow() все еще работает довольно (удивительно!) ну для этой цели, и абсолютно важно включить как можно меньше в стандартную библиотеку C, чтобы ее можно было сделать как можно более портативной и простой в реализации. С другой стороны, это вовсе не мешает находясь в стандартной библиотеке C++ или STL, которую я уверен, что никто не планирует использовать в какой-то встроенной платформе.

теперь, для длинного ответа.

pow(x, n) можно сделать гораздо быстрее во многих случаях, специализируясь n к натуральному числу. Мне пришлось использовать мою собственную реализацию этой функции почти для каждой программы, которую я пишу (но я пишу много математических программ на C). Специализированная операция может быть выполнена в O(log(n)) времени, но когда n мало, более простая линейная версия может быть быстрее. Вот реализации обоих:


    // Computes x^n, where n is a natural number.
    double pown(double x, unsigned n)
    {
        double y = 1;
        // n = 2*d + r. x^n = (x^2)^d * x^r.
        unsigned d = n >> 1;
        unsigned r = n & 1;
        double x_2_d = d == 0? 1 : pown(x*x, d);
        double x_r = r == 0? 1 : x;
        return x_2_d*x_r;
    }
    // The linear implementation.
    double pown_l(double x, unsigned n)
    {
        double y = 1;
        for (unsigned i = 0; i < n; i++)
            y *= x;
        return y;
    }

(я оставила x и возвращаемое значение удваивается, потому что результат pow(double x, unsigned n) поместится в двойник примерно так же часто, как pow(double, double) будет.)

(да, pown является рекурсивным, но разбить стек абсолютно невозможно, так как максимальный размер стека будет примерно равен log_2(n) и n - целое число. Если n - Это 64-разрядное целое число, которое дает вам максимальный размер стека около 64. нет аппаратное обеспечение имеет такие крайние ограничения памяти, за исключением некоторых изворотливых фотографий с аппаратными стеками, которые идут только от 3 до 8 вызовов функций глубоко.)

что касается производительности, вы будете удивлены тем, что разнообразия сада pow(double, double) способен. Я протестировал сто миллионов итераций на своем 5-летнем IBM Thinkpad с x равно числу итераций и n равна 10. В этом случае pown_l выиграл. в glibc pow() взял 12.0 пользователей секунды,pown заняло 7,4 секунды пользователя, и pown_l заняло всего 6,5 секунды. Так что ничего удивительного. Мы более или менее ожидали этого.

тогда, я позволю x быть постоянным (я установил его в 2.5), и я зациклился n от 0 до 19 сто миллионов раз. На этот раз, совершенно неожиданно, glibc pow выиграл, и с большим отрывом! Он взял только 2.0 секунд пользователей. Мой pown заняло 9,6 секунды, и pown_l взял 12.2 секунд. Что здесь произошло? Я сделал еще один тест, чтобы найти из.

я сделал то же самое только с x равна миллиону. На этот раз, pown выиграл в 9,6 сек. pown_l взял 12.2 s и glibc pow взял 16.3 s. Теперь все ясно! в glibc pow работает лучше, чем три, когда x низкий, но хуже, когда x высока. Когда x высокая pown_l лучше n - это низко, и pown лучше x высока.

Итак, вот три разных алгоритма, каждый из которых способен выполнять лучше, чем другие при определенных обстоятельствах. Итак, в конечном счете, что использовать, скорее всего, зависит от того, как вы планируете использовать pow, но используя правильную версию и стоит того, и иметь все версии приятно. Фактически, вы даже можете автоматизировать выбор алгоритма с помощью такой функции:

double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
    if (x_expected < x_threshold)
        return pow(x, n);
    if (n_expected < n_threshold)
        return pown_l(x, n);
    return pown(x, n);
}

пока x_expected и n_expected являются константами, решенными во время компиляции, наряду с, возможно, некоторыми другими предостережениями, оптимизирующим компилятором стоит его соли автоматически удалить весь pown_auto вызовите функцию и замените ее соответствующим выбором из трех алгоритмов. (Теперь, если вы действительно собираетесь попытаться использовать это, вам, вероятно, придется немного поиграть с ним, потому что я точно не пытался сборка то, что я написал выше. ;))

С другой стороны, glibc pow работает и glibc уже достаточно большой. Стандарт C должен быть портативный, в том числе для различных встроенные устройства (на самом деле встроенные разработчики везде в целом согласны с тем, что glibc уже слишком велик для них), и он не может быть портативным, если для каждой простой математической функции он должен включать каждый альтернативный алгоритм, который может будет использовать. Так вот почему это не в стандарте C.

сноска: в тестировании производительности времени я дал своим функциям относительно щедрые флаги оптимизации (-s -O2), которые вероятно, будет сопоставимо, если не хуже, чем то, что, вероятно, использовалось для компиляции glibc в моей системе (archlinux), поэтому результаты, вероятно, справедливы. Для более строгого теста мне пришлось бы скомпилировать glibc самостоятельно, и я reeeally не хочется этого делать. Я использовал Gentoo, поэтому я помню, сколько времени это занимает, даже когда задача автоматическое. Результаты для меня достаточно убедительны (или, скорее, неубедительны). Вы, конечно, можете сделать это себе.

бонусный раунд: специализация pow(x, n) для всех чисел инструментальные если требуется точный целочисленный вывод, что и происходит. Рассмотрим выделение памяти для N-мерного массива с P^N элементами. Получение p^N даже одним приведет к возможному случайному segfault.


одна из причин, по которой C++ не имеет дополнительных перегрузок, должна быть совместима с C.

C++98 имеет такие функции, как double pow(double, int), но они были удалены в C++11 с аргументом, что C99 не включал их.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3286.html#550

получение немного более точного результата также означает получение немного разные результат.


возможно, потому, что ALU процессора не реализовал такую функцию для целых чисел, но есть такая инструкция FPU (как указывает Стивен, это на самом деле пара). Таким образом, на самом деле было быстрее бросить в double, вызвать pow с двойниками, затем проверить переполнение и отбросить назад, чем реализовать его с помощью целочисленной арифметики.

(во-первых, логарифмы уменьшают мощности до умножения, но логарифмы целых чисел теряют большую точность для большинства входы)

Стивен прав, что на современных процессорах это уже не так, но стандарт C, когда были выбраны математические функции (C++ просто использовал функции C), теперь что, 20 лет?


мир постоянно развивается, как и языки программирования. The четвертая часть c десятичной TR1 добавляет несколько функций <math.h>. Два семейства этих функций могут представлять интерес для этого вопроса:

  • на pown функции, которые принимают число с плавающей запятой и intmax_t экспонента.
  • на powr функции, которые принимают два числа с плавающей запятой (x и y) и вычислить x к власти y по формуле exp(y*log(x)).

похоже, что стандартные ребята в конечном итоге сочли эти функции достаточно полезными для интеграции в стандартную библиотеку. Однако рациональным является то, что эти функции рекомендуются ISO / IEC / IEEE 60559: 2011 стандартные для двоичных и десятичных чисел с плавающей точкой. Я не могу точно сказать, какой "стандарт" был соблюден во время C89, но будущие эволюции <math.h> будет, наверное в значительной степени под влиянием будущих эволюций ISO / IEC / IEEE 60559 стандартные.

обратите внимание, что четвертая часть десятичного TR не будет включена в C2x (следующая основная версия C) и, вероятно, будет включена позже в качестве дополнительной функции. Не было никакого намерения, о котором я знаю, включить эту часть TR в будущую редакцию c++.


1 Вы можете найти некоторую незавершенную документацию здесь.


на самом деле, это так.

поскольку C++11 существует шаблонная реализация pow(int, int) --- и даже более общие случаи, см. (7) в http://en.cppreference.com/w/cpp/numeric/math/pow


очень простая причина:

5^-2 = 1/25

все в библиотеке STL основано на самом точном, надежном материале, который только можно себе представить. Конечно, инт вернется к нулю (от 1/25), но это будет неточный ответ.

Я согласен, в некоторых случаях это странно.