Я всегда задавался вопросом, как я могу сделать функцию, которая вычисляет питание (например, 23) сам. На большинстве языков они включены в стандартную библиотеку, главным образом как pow(double x, double y)
, но как я могу записать это сам?
Я думал о for loops
, но это думает, что мой мозг вошел в цикл (когда я хотел сделать питание с экспонентой нецелого числа, как 54,5 или отрицательные стороны 2-21), и я сошел с ума ;)
Так, как я могу записать функцию, которая вычисляет питание вещественного числа?Спасибо
О, возможно, важный для примечания: Я не могу использовать функции, которые используют полномочия (например. exp
), который сделал бы это в конечном счете бесполезным.
Отрицательные степени не проблема, они просто инверсия ( 1 / x
) положительной мощности.
Степени с плавающей запятой немного сложнее; как вы знаете, дробная степень эквивалентна корню (например, x ^ (1/2) == sqrt (x)
), и вы также знаете, что умножение степеней с одинаковым основанием эквивалентно сложению их показателей .
С помощью всего вышеперечисленного вы можете:
Пример:
2^(-3.5) = (2^3 * 2^(1/2)))^-1 = 1 / (2*2*2 * sqrt(2))
По определению:
a^b = exp(b ln(a))
где exp(x) = 1 + x + x^2/2 + x^3/3! + x^4/4! + x^5/5! + ...
где n! = 1 * 2 * ... * n
.
На практике можно хранить массив из первых 10 значений 1/n!
, а затем приближенно
exp(x) = 1 + x + x^2/2 + x^3/3! + ... + x^10/10!
потому что 10! - огромное число, поэтому 1/10! очень мало (2.7557319224⋅10^-7).
Функции Вольфрама дают большое количество формул для вычисления степеней. Некоторые из них было бы очень просто реализовать.
Лучшим алгоритмом для эффективного вычисления положительных целых чисел является многократное возведение в квадрат основания с учетом дополнительных остатков от умножения. Вот пример решения на языке Python, который будет относительно легко понять и перевести на ваш язык:
def power(base, exponent):
remaining_multiplicand = 1
result = base
while exponent > 1:
remainder = exponent % 2
if remainder > 0:
remaining_multiplicand = remaining_multiplicand * result
exponent = (exponent - remainder) / 2
result = result * result
return result * remaining_multiplicand
Чтобы заставить его работать с отрицательными экспонентами, достаточно вычислить положительную версию и разделить 1 на результат, так что это будет простой модификацией приведенного выше кода. Дробные экспоненты значительно сложнее, поскольку это означает вычисление n-го корня из основания, где n = 1/abs(exponent % 1)
и умножение результата на результат вычисления мощности целой части:
power(base, exponent - (exponent % 1))
Вы можете вычислить корни до желаемого уровня точности, используя метод Ньютона. Посмотрите статью в Википедии об этом алгоритме.
A B = Log -1 (Log (A) * B)
Изменить: да, это определение действительно дает кое-что полезное. Например, на x86 он почти напрямую преобразуется в FYL2X
(Y * Log 2 (X)) и F2XM1
(2 x ] -1):
fyl2x
fld st(0)
frndint
fsubr st(1),st
fxch st(1)
fchs
f2xmi
fld1
faddp st(1),st
fscale
fstp st(1)
Код оказывается немного длиннее, чем вы могли ожидать, в первую очередь потому, что F2XM1
работает только с числами в диапазоне -1.0..1.0. Часть fld st (0) / frndint / fsubr st (1), st
вычитает целую часть, поэтому остается только дробь. Мы применяем к нему F2XM1
, снова добавляем 1, затем используем FSCALE
для обработки целой части возведения в степень.
Обычно реализация функции pow (double, double)
в математических библиотеках основана на идентификаторе:
pow(x,y) = pow(a, y * log_a(x))
Используя этот идентификатор, вам нужно только знать, как возвести единичное число a
в произвольный показатель степени и как взять основание логарифма a
. Вы эффективно превратили сложную функцию с несколькими переменными в две функции одной переменной и умножение, которое довольно легко реализовать. Наиболее часто выбираемые значения a
- это e
или 2
- e
, поскольку e ^ x
и log_e (1 + x)
имеет несколько очень хороших математических свойств, а 2
, потому что он имеет несколько хороших свойств для реализации в арифметике с плавающей запятой.
Загвоздка этого способа заключается в том, что (если вы хотите получить полную точность) вам нужно вычислить член log_a (x)
(и его произведение с y
) с более высокой точностью, чем представление с плавающей запятой x
и y
.Например, если x
и y
являются двойными, и вы хотите получить результат с высокой точностью, вам нужно придумать способ хранения промежуточных результатов (и выполнять арифметические операции ) в формате более высокой точности. Формат Intel x87 является обычным выбором, как и 64-битные целые числа (хотя, если вам действительно нужна высококачественная реализация, вам нужно будет выполнить пару 96-битных целочисленных вычислений, которые в некоторых случаях немного болезненны. языков). С этим намного легче справиться, если вы реализуете powf (float, float)
, потому что тогда вы можете просто использовать double
для промежуточных вычислений. Я бы рекомендовал начать с этого, если вы хотите использовать этот подход.
Описанный мной алгоритм - не единственный возможный способ вычисления pow
. Он просто наиболее подходит для получения высокоскоростного результата, который удовлетворяет фиксированному априорному пределу точности. Он менее подходит в некоторых других контекстах и, безусловно, намного сложнее реализовать, чем алгоритм вычисления квадратного корня, который предлагали некоторые другие.
Если вы хотите попробовать алгоритм повторения квадратного [корня], начните с написания беззнаковой целочисленной степенной функции, которая использует только повторное возведение в квадрат. Как только вы хорошо усвоите алгоритм для этого сокращенного случая, вы обнаружите, что довольно просто расширить его для обработки дробных показателей.
Есть два разных случая, с которыми нужно иметь дело: целые показатели и дробные показатели.
Для целочисленных показателей вы можете использовать возведение в степень возведением в квадрат.
def pow(base, exponent):
if exponent == 0:
return 1
elif exponent < 0:
return 1 / pow(base, -exponent)
elif exponent % 2 == 0:
half_pow = pow(base, exponent // 2)
return half_pow * half_pow
else:
return base * pow(base, exponent - 1)
Второй «elif» - это то, что отличает эту функцию от наивной функции pow. Это позволяет функции выполнять рекурсивные вызовы O (log n) вместо O (n).
Для дробных показателей вы можете использовать тождество a ^ b = C ^ (b * log_C (a)). Удобно взять C = 2, поэтому a ^ b = 2 ^ (b * log2 (a)). Это сводит проблему к написанию функций для 2 ^ x и log2 (x).
Причина, по которой удобно использовать C = 2, заключается в том, что числа с плавающей запятой хранятся в базе 2 с плавающей запятой. log2 (a * 2 ^ b) = log2 (a) + b. Это упрощает написание вашей функции log2: вам не нужно, чтобы она была точной для каждого положительного числа, только на интервале [1, 2). Точно так же, чтобы вычислить 2 ^ x, вы можете умножить 2 ^ (целая часть x) * 2 ^ (дробная часть x). Первую часть легко сохранить в виде числа с плавающей запятой, для второй части вам просто понадобится функция 2 ^ x в интервале [0, 1).
Сложнее всего найти хорошее приближение 2 ^ x и log2 (x). Простой подход - использовать ряд Тейлора .
Для положительных целых степеней посмотрите на возведение в квадрат и возведение в степень .
Это интересное упражнение. Вот несколько советов, которые вы должны попробовать в следующем порядке: