Чому в стандартних бібліотеках C ++ немає `int pow (int base, int exponent)"?


116

Я відчуваю, що я просто не можу його знайти. Чи є якась причина, що powфункція C ++ не реалізує функцію "живлення" ні для чого, крім floats і doubles?

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


4
Це гарне запитання, і я не думаю, що відповіді мають багато сенсу. Негативні показники не працюють? Візьміть неподписані вставки як експоненти. Більшість входів викликає переповнення? Те саме стосується exp та double pow, я не бачу, щоб ніхто скаржився. То чому ця функція не є стандартом?
static_rtti

2
@static_rtti: "Те ж саме стосується досвіду та подвійного пороху" абсолютно неправдиво. Я докладно докладу свою відповідь.
Стівен Канон

11
У стандартній бібліотеці C ++ double pow(int base, int exponent)з C ++ 11 (§26.8 [c.math] / 11 пункт 2)
Cubbi

Потрібно вирішити, що "реалізація тривіальна" та "писати не цікаво".
Маркіз Лорн

Відповіді:


66

Станом C++11на рік до набору функцій живлення (та інших) були додані особливі випадки. C++11 [c.math] /11констатує, перерахувавши всі float/double/long doubleперевантаження (мій акцент і перефразоване):

Крім того, повинні бути додаткові перевантаження, достатні для того, щоб, якщо будь-який аргумент, що відповідає doubleпараметру, має тип doubleабо цілий тип, то всі аргументи, що відповідають doubleпараметрам, ефективно передаються double.

Таким чином, в основному цілі параметри будуть виконані вдвічі для виконання операції.


До цього C++11(коли було поставлено запитання), не було жодних цілих перевантажень.

Так як я не був ні тісно пов'язаний з творцями , Cні C++в дні їх створення (хоча я є досить старим), ні частина комітетів ANSI / ISO , які створили стандарти, це обов'язково думка по моїй частині. Мені б хотілося подумати, що це усвідомлена думка, але, як скаже вам моя дружина (часто і без особливого заохочення), я раніше помилявся :-)

Припущення, для чого воно варте, випливає.

Я підозрюю, що причина, в якій початковий попередній ANSI Cне мала цієї функції, полягає в тому, що вона була абсолютно непотрібною. По-перше, вже був ідеально хороший спосіб виконання цілих повноважень (з подвоєннями, а потім просто перетворенням назад у ціле число, перевірка на переповненість чи переливання цілих чисел перед перетворенням).

По-друге, ще одна річ, яку ви повинні пам’ятати, - це те, що початкова інтенція Cбула як мова програмування систем , і сумнівно, чи бажана плаваюча точка на цій арені взагалі.

Оскільки одним із випадків його початкового використання було кодування UNIX, плаваюча точка була б поруч із марною. BCPL, на якому базувався C, також не використовував повноважень (у пам'яті він взагалі не мав плаваючої точки).

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

По-третє, оскільки реалізація інтегральної потужності є відносно тривіальною, майже впевнено, що розробники мови краще використають свій час, надаючи більше корисних речей (див. Нижче коментарі щодо можливих витрат).

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

Щодо того, чому він раніше не був доданий до стандартів C++11, ви повинні пам’ятати, що органи, що встановлюють стандарти, мають конкретні рекомендації, яких слід дотримуватися. Наприклад, ANSI Cбуло спеціально поставлено завдання кодифікувати існуючу практику, а не створювати нову мову. В іншому випадку вони могли зійти з розуму і дали нам Ада :-)

Пізніші ітерації цього стандарту також мають конкретні вказівки і їх можна знайти в документах, що обґрунтовують (обґрунтування того, чому комітет приймав певні рішення, а не обґрунтування самої мови).

Наприклад, C99документ обґрунтування конкретно містить два C89керівних принципи, які обмежують те, що можна додати:

  • Зберігайте мову маленькою та простою.
  • Надайте лише один спосіб зробити операцію.

Вказівки (не обов'язково конкретні ) встановлюються для окремих робочих груп, а отже, обмежують також C++комітети (та всі інші групи ISO).

Крім того, органи, що встановлюють стандарти, розуміють, що для кожного прийнятого рішення існує можлива вартість (економічний термін, який означає, що вам доведеться відмовитися від прийнятого рішення). Наприклад, альтернативна вартість придбання 10 000 доларів убер-ігрового автомата - це сердечні відносини (або, мабуть, усі стосунки) з вашою другою половиною протягом приблизно півроку.

Ерік Ганнерсон пояснює це добре своїм поясненням -100 балів , чому речі не завжди додаються до продуктів Microsoft - в основному функція починає 100 балів у лунці, тому вона повинна додати трохи вартості, щоб її навіть врахували.

Іншими словами, чи бажаєте ви скористатись інтегральним енергооператором (який, чесно кажучи, будь-який напівпристойний кодер міг би згорнути за десять хвилин) або багаторядне додавання в стандарт? Для себе я вважаю за краще мати останнє, і не доведеться співучатися з різними реалізаціями під UNIX та Windows.

Я хотів би також побачити тисячі і тисячі колекцій стандартної бібліотеки (хеші, бтрей, червоно-чорні дерева, словник, довільні карти тощо), але, як обґрунтовує:

Стандарт - це договір між виконавцем та програмістом.

А кількість виконавців у органах зі стандартів набагато перевищує кількість програмістів (або принаймні тих програмістів, які не розуміють можливих витрат). Якби все це було додано, наступний стандарт C++був би C++215xі, ймовірно, був би повністю реалізований розробниками-компіляторами через триста років після цього.

У всякому разі, це мої (досить об’ємні) думки з цього приводу. Якби тільки голоси роздали основи на кількість, а не на якість, я незабаром всіх інших виду з води. Спасибі за слухання :-)


2
FWIW, я не думаю, що C ++ дотримується "Надати лише один спосіб зробити операцію" як обмеження. Правильно, тому що, наприклад, to_stringлямбда - це зручність для речей, які ви вже могли зробити. Я припускаю, що можна інтерпретувати "лише один спосіб зробити операцію" дуже вільно, щоб дозволити обидва ці можливості, і в той же час дозволити практично будь-яке дублювання функціональності, яке можна собі уявити, сказавши "ага! Ні! Тому що зручність робить це тонко відмінна операція від точно еквівалентної, але більш тривалої альтернативи! ". Що, безумовно, стосується лямбда.
Стів Джессоп

@Steve, так, це було погано сформульовано з мого боку. Точніше сказати, що для кожного комітету є рекомендації, а не всі комітети дотримуються одних і тих же вказівок. Коригувана відповідь на уточнення
paxdiablo

2
Всього один момент (із кількох): "будь-яка мавпа з кодом могла збитися за десять хвилин". Звичайно, і якщо 100 мавп з кодом (хороший образливий термін, BTW) роблять це щороку (напевно, низька оцінка), ми витрачаємо 1000 хвилин. Дуже ефективно, не думаєте?
Юрген А. Ерхард

1
@ Юрген, це не мало бути образливим (оскільки я фактично не приписав етикетку нікому конкретно), це був лише показник, який powнасправді не вимагає особливих навичок. Звичайно , я волів би мати стандарт забезпечити то , що буде вимагати великої майстерності, і привести до набагато більш даремно хвилин , якщо зусилля повинно було бути продубльовані.
paxdiablo

2
@ eharo2, просто замініть "наполовину пристойний кодер" у поточному тексті на "кодова мавпа". Я не вважав, що це також ображає, але я вважав, що найкраще бути обережним і, якщо чесно, нинішнє формулювання поширюється на ту саму ідею.
paxdiablo

41

Для будь-якого інтегрального типу фіксованої ширини, майже всі можливі вхідні пари в будь-якому випадку переповнюють тип. У чому полягає стандартизація функції, яка не дає корисного результату для більшості можливих входів?

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


Редагувати: У коментарі до запитання static_rtti пише: "Більшість входів спричиняє переповнення? Це ж стосується і досвіду exp та подвійного пороху, я не бачу, щоб хтось скаржився". Це неправильно.

Залишимо осторонь exp, бо це поруч із сутью (хоча це насправді зробить мою справу сильнішою), і зосередимось на double pow(double x, double y). Для якої частини пар (x, y) ця функція робить щось корисне (тобто не просто переповнення чи переповнення)?

Я фактично збираюся зосередитись лише на невеликій частині вхідних пар, для якої powє сенс, тому що цього буде достатньо, щоб довести мою думку: якщо x позитивний і | y | <= 1, то powне переливається і не переливається. Це включає майже чверть усіх пар з плаваючою комою (рівно половина номерів, що не мають NaN з плаваючою комою, є позитивними, і лише менше половини не-NaN чисел з плаваючою комою мають величину менше 1). Очевидно, існує багато інших пар вхідних даних, powякі дають корисні результати, але ми переконалися, що це принаймні чверть усіх вхідних даних.

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

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

Таким чином, із вхідних пар 2 ^ (2n) менше 2 ^ (n + 1) + 2 ^ (3n / 2) дають значущі результати. Якщо ми подивимось, що, мабуть, найпоширеніше використання, 32-бітні цілі числа, це означає, що щось на порядку 1/1000-го одного відсотка вхідних пар не просто переповнюється.


8
Все одно це суперечки. Тільки тому, що функція не є дійсною для деяких або багатьох входів, вона не робить її менш корисною.
static_rtti

2
@static_rtti: pow(x,y)не переливається на нуль для жодного x, якщо | y | <= 1. Існує дуже вузька смуга входів (велика х, y дуже майже -1), для якої відбувається підтік, але результат все ще має значення в цьому діапазоні.
Стівен Канон

2
Задумавшись більше, я погоджуюсь на підтік. Я все ще думаю, що це не стосується питання.
static_rtti

7
@ybungalobill: Чому ви вибрали це як причину? Персонелі, я віддаю перевагу корисності для великої кількості проблем та програмістів, можливість виготовити оптимізовані за допомогою версії версії, які швидші, ніж наївна реалізація, напевно, написають більшість програмістів тощо. Ваш критерій здається абсолютно довільним, і, чесно кажучи, зовсім безглуздим.
static_rtti

5
@StephenCanon: З іншого боку, ваш аргумент показує, що очевидно правильна і оптимальна реалізація цілого числа pow- це просто крихітна таблиця пошуку. :-)
R .. GitHub ЗАСТАНОВИТЬ ДІЙ

11

Тому що немає можливості представити всі цілі повноваження в int так чи інакше:

>>> print 2**-4
0.0625

3
Для числового типу обмеженого розміру немає можливості представити всі повноваження цього типу в межах цього типу через переповнення. Але ваша думка про негативні сили є більш достовірною.
Кріс Лутц

1
Я бачу негативні експоненти як щось, що стандартна реалізація може вирішити, або взявши неподписаний int як показник, або повернувши нуль, коли негативний показник відображається як вхід, а int - очікуваний вихід.
Dan O

3
або мати окремий int pow(int base, unsigned int exponent)іfloat pow(int base, int exponent)
Ponkadoodle

4
Вони могли просто оголосити це невизначеною поведінкою для передачі від'ємного цілого числа.
Йоханнес Шауб - ліб

2
У всіх сучасних реалізаціях все, що виходить за межі int pow(int base, unsigned char exponent), все одно дещо марне. Або база дорівнює 0, або 1, і показник не має значення, це -1, в цьому випадку має значення лише останній біт показника, або base >1 || base< -1в такому випадку exponent<256на штраф переповнення.
MSalters

9

Це насправді цікаве питання. Один аргумент, який я не знайшов у дискусії, - це проста відсутність очевидних зворотних значень аргументів. Давайте порахуємо способи, де гіптетична int pow_int(int, int)функція могла вийти з ладу.

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

Функція має щонайменше 2 режими відмови. Цілі особи не можуть представляти ці значення, поведінку функції в цих випадках потрібно визначати стандартом - і програмістам потрібно знати, як саме функціонує функція цих випадків.

Загальний вихід з функції здається єдиним розумним варіантом. Програміст може використовувати версію з плаваючою комою з усіма доступними повідомленнями про помилки.


Але хіба перші два випадки не застосовуватимуться також і до powміж поплавками? Візьміть два великих поплавця, підніміть один до сили іншого і у вас є Перелив. І pow(0.0, 0.0)спричинив би ту саму проблему, що і ваш другий пункт. Ваша третя точка - єдина реальна різниця між реалізацією функції живлення для цілих чисел проти плавців.
numbermaniac

7

Коротка відповідь:

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

Тепер для довгої відповіді.

pow(x, n)можна зробити набагато швидше у багатьох випадках, спеціалізуючись nна натуральній кількості. Мені довелося використовувати власну реалізацію цієї функції майже для кожної програми, яку я пишу (але я пишу багато математичних програм на С). Спеціалізовану операцію можна зробити 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 сто мільйонів разів. Цього разу, зовсім несподівано, powпереміг glibc , і зсув! Це зайняло всього 2,0 секунди користувача. Моє pownзайняло 9,6 секунди, а pown_lзайняло 12,2 секунди. Що тут сталося? Я зробив ще один тест, щоб з’ясувати.

Я робив те саме, що вище, лише з xрівнем мільйона. Цього разу pownвиграв у 9,6с. pown_lвзяв 12,2s, а глібк-порох - 16,3s. Тепер це зрозуміло! 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 для них уже занадто великий), і він не може бути портативним, якщо для кожної простої математичної функції потрібно включати кожен альтернативний алгоритм, який може бути корисним. Отже, це не відповідає стандарту С.

виноска: під час тестування продуктивності часу я надав своїм функціям відносно щедрі прапори оптимізації ( -s -O2), які, ймовірно, можна порівняти, якщо не гірше, ніж те, що, ймовірно, використовувалося для компіляції glibc у моїй системі (archlinux), тому результати, ймовірно, справедливий Для більш ретельної перевірки, я мав би скласти Glibc себе , і я reeeally не відчуваю , як це робити. Раніше я використовував Gentoo, тому я пам’ятаю, скільки часу займає, навіть коли завдання автоматизоване . Результати для мене достатньо переконливі (а точніше непереконливі). Звичайно, ви можете зробити це самостійно.

Раунд бонусів: спеціалізація pow(x, n)на всі цілі числа є важливою, якщо потрібен точний цілий вихід, що і відбувається. Розглянемо виділення пам'яті для N-мірного масиву з елементами p ^ N. Якщо вимкнути p ^ N навіть по одному, це призведе до можливого випадкового виникнення segfault.


Я думаю, якщо ви позбудетесь від рекурсії, ви заощадите час, необхідний для розподілу стека. І так, у нас виникла ситуація, коли Pow все сповільнював і нам доводилося реалізовувати свою власну.
Самбатіон

"Ніхто не має таких екстремальних обмежень пам'яті" помилково. PIC часто має обмежений стек викликів максимум від 3 (наприклад, PIC10F200) до 8 (наприклад, 16F722A) викликів (PIC використовує апаратний стек для функціональних дзвінків).
12431234123412341234123

о, людина, яка жорстока хаха. Гаразд, тому він не працюватиме на цих ПІК.
загадковийфізик

Для цілої бази, а також потужності, як і питання, про яке задається питання, компілятори (gcc і clang) легко створюють цикл без гілок з ітеративної (замість рекурсивної) реалізації. Це дозволяє уникнути непередбачуваних галузевих непередбачуваних дій n. godbolt.org/z/L9Kb98 . gcc і clang не в змозі оптимізувати ваше рекурсивне визначення у простий цикл, а насправді роблять гілку на кожен біт n. (Поки pown_iter(double,unsigned)вони все ще гілляться, але реалізація SSE2 або SSE4.1 без розгалужень повинна бути можливою в x86 asm або з вбудованими C. Але навіть це краще, ніж рекурсія)
Пітер Кордес

Лайно, тепер я маю робити тести знову і знову на основі циклу, щоб бути впевненим. Я буду думати про це.
загадковийфізик

6

Однією з причин 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

Отримати трохи точніший результат також означає отримати трохи інший результат.


3

Світ постійно розвивається, як і мови програмування. Четверта частина C десяткового TR ¹ додає ще кілька функцій <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 ++.


Some Деякі документації, що незавершені, ви можете знайти тут .


Чи є якісь правдоподібні реалізації, в яких за pownдопомогою експонента більше, ніж LONG_MAXколи-небудь, вийде значення, відмінне від використання LONG_MAX, або де значення, менше, ніж LONG_MINповинно дати значення, відмінне від LONG_MIN? Цікаво, яку користь отримує від використання intmax_tдля показника?
supercat

@supercat Не маю ідеї, вибачте.
Морвен

Можливо, варто згадати, що, дивлячись на Стандарт, здається, він також визначає необов'язкову функцію "crpown", яка, якщо буде визначена, буде правильно закругленою версією "pown"; Стандарт інакше не визначає необхідний ступінь точності. Реалізувати швидку та помірно точну "попі" легко, але забезпечити правильне округлення у всіх випадках може бути набагато дорожче.
supercat

2

Можливо, тому, що ALU процесора не реалізував таку функцію для цілих чисел, але є така інструкція FPU (як вказує Стівен, це насправді пара). Таким чином, насправді було швидше кинути подвоїтись, викликати Pow із подвоєнням, а потім перевірити на переповнення та відкинути назад, ніж реалізувати це, використовуючи цілу арифметику.

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

Стівен має рацію, що для сучасних процесорів це вже не відповідає дійсності, але стандарт C, коли вибиралися математичні функції (C ++ щойно використовував функції C), що зараз, 20 років?


5
Я не знаю жодної поточної архітектури з інструкцією FPU pow. x86 має y log2 xінструкцію ( fyl2x), яку можна використовувати як першу частину powфункції, але powфункція, написана таким чином, займає сотні циклів для виконання поточного обладнання; добре написана ціла ціла програма експоненціації в кілька разів швидша.
Стівен Канон

Я не знаю, що "сотні" є точними, здається, це близько 150 циклів для fyl2x, а потім f2xm1 на більшості сучасних процесорів, і це отримує конвеєрні інструкції з іншими інструкціями. Але ви праві, що добре налаштована ціла реалізація повинна бути набагато швидшою (в наші дні), оскільки IMUL було витрачено набагато більше, ніж інструкції з плаваючою комою. Коли ще був написаний стандарт C, IMUL був досить дорогим, і його використання в циклі, ймовірно, зайняло більше часу, ніж використання FPU.
Ben Voigt

2
Змінив моє голосування з огляду на виправлення; все ж майте на увазі (a) що стандарт C зазнав значної редакції (включаючи велике розширення математичної бібліотеки) у 1999 році, і (b) що стандарт C не записаний у будь-яку конкретну архітектуру процесора - наявність або відсутність інструкцій FPU на x86 по суті не має нічого спільного з тим, яку функціональність комітет C вирішує стандартизувати.
Стівен Канон

Це не прив’язано до жодної архітектури, правда, але відносна вартість інтерполяції таблиці пошуку (як правило, використовується для реалізації плаваючої точки) порівняно з цілим множенням змінилася майже однаково для всіх архітектур, я б здогадався.
Бен Войгт

1

Ось дійсно проста O (log (n)) реалізація pow (), яка працює для будь-яких числових типів, включаючи цілі числа :

template<typename T>
static constexpr inline T pown(T x, unsigned p) {
    T result = 1;

    while (p) {
        if (p & 0x1) {
            result *= x;
        }
        x *= x;
        p >>= 1;
    }

    return result;
}

Це краще, ніж реалізація загадковогоPhysicist O (log (n)), оскільки не використовує рекурсії.

Це майже завжди швидше, ніж його лінійна реалізація (доки p> ~ 3), оскільки:

  • для цього не потрібна додаткова пам'ять
  • це робить лише ~ 1,5x більше операцій за цикл
  • це лише робить ~ 1,25x більше оновлень пам’яті за цикл

-2

Власне кажучи, це і є.

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


EDIT: пуристи можуть стверджувати, що це неправильно, оскільки фактично використовується "рекламоване" введення тексту. Так чи інакше, ви отримуєте правильний intрезультат або помилку щодо intпараметрів.


2
це неправильно. Перевантаження (7), pow ( Arithmetic1 base, Arithmetic2 exp )яке буде передано doubleабо long doubleякщо ви прочитали опис: "7) Набір перевантажень або шаблон функції для всіх комбінацій аргументів арифметичного типу, не охоплених 1-3). Якщо будь-який аргумент має інтегральний тип, він передається в подвійний. Якщо будь-який аргумент довгий подвійний, то тип повернення, що сприяє, також довгий подвійний, інакше тип повернення завжди подвійний. "
phuclv

що тут неправильно? Я просто сказав, що в даний час (з моменту C ++ 11) в стандартній бібліотеці є шаблонна пара ( , ), що не було в 2010 році.
Діма Пасечник,

5
Ні, це не так. Храмники просувають ці типи вдвічі або вдвічі. Так це працює на подвійних під них.
Трисмегист

1
@Trismegistos Це все ще дозволяє int параметри. Якщо цього шаблону там не було, передача параметрів int змушує його інтерпретувати біти в int як float, викликаючи довільні несподівані результати. Те саме відбувається із змішаними вхідними значеннями. наприклад , pow(1.5f, 3)= 1072693280а pow(1.5f, float(3))=3.375
Марк Ієронім

2
ОП попросили int pow(int, int), але C ++ 11 лише надає double pow(int, int). Дивіться пояснення @phuclv.
xuhdev

-4

Дуже проста причина:

5^-2 = 1/25

Все в бібліотеці STL базується на найбільш точних, надійних речах, які можна уявити. Звичайно, int повернеться до нуля (від 1/25), але це була б неточна відповідь.

Я згоден, в деяких випадках це дивно.


3
Очевидно, що вимагається непідписаний другий аргумент. Існує багато додатків, які вимагають лише невід'ємних цілих повноважень.
загадковийфізик
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.