Как я могу написать степенную функцию сам? - PullRequest
49 голосов
/ 21 мая 2010

Мне всегда было интересно, как я могу сделать функцию, которая сама рассчитывает мощность (например, 2 3 ). На большинстве языков они включены в стандартную библиотеку, в основном как pow(double x, double y), но как я могу написать это сам?

Я думал о for loops, но он думал, что мой мозг оказался в цикле (когда я хотел сделать мощность с нецелым показателем, например, 5 4.5 или отрицательные значения 2 -21 ) и я сошел с ума;)

Итак, как мне написать функцию, которая вычисляет мощность действительного числа? Спасибо


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

Ответы [ 13 ]

48 голосов
/ 21 мая 2010

Отрицательные силы не проблема, они просто обратная (1/x) положительной силы.

Сила с плавающей запятой немного сложнее; как вы знаете, дробная сила эквивалентна корню (например, x^(1/2) == sqrt(x)), и вы также знаете, что умножение степеней с одной и той же базой эквивалентно добавлению их показателей.

Имея все вышеперечисленное, вы можете:

  • Разложить показатель степени на целую часть и рациональную часть .
  • Рассчитать целочисленную мощность с помощью цикла (вы можете оптимизировать его, разлагая на факторы и повторно используя частичные вычисления).
  • Рассчитайте корень по любому алгоритму, который вам нравится (любая итерационная аппроксимация, например, деление пополам или метод Ньютона, может работать).
  • Умножьте результат.
  • Если показатель был отрицательным, примените обратное.
* * 1 022 Пример: * 1 023 *
2^(-3.5) = (2^3 * 2^(1/2)))^-1 = 1 / (2*2*2 * sqrt(2))
22 голосов
/ 21 мая 2010

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 для обработки целочисленной части возведения в степень.

19 голосов
/ 21 мая 2010

Обычно реализация функции 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. Это просто самое подходящее средство для получения высокоскоростного результата, который удовлетворяет фиксированной границе точности a priori . Он менее пригоден в некоторых других контекстах и, безусловно, гораздо сложнее реализовать, чем алгоритм повторного квадрата [корня], предложенный некоторыми другими.

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

9 голосов
/ 23 мая 2010

Существует два различных случая: целочисленные показатели и дробные показатели.

Для целочисленных показателей можно использовать возведение в степень в квадрате.

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)

Второй «элиф» - это то, что отличает это от наивной функции пау. Это позволяет функции совершать 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). Простой подход - использовать ряд Тейлора .

7 голосов
/ 21 мая 2010

По определению:

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).

4 голосов
/ 21 мая 2010

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

4 голосов
/ 21 мая 2010

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

2 голосов
/ 03 ноября 2014

Используя три самостоятельно реализованные функции iPow(x, n), Ln(x) и Exp(x), я могу вычислить fPow(x, a), x и существо , удваивающее . Ни одна из функций ниже не использует библиотечные функции, а только итерацию.

Некоторые пояснения о реализованных функциях:

(1) iPow(x, n): x равен double, n равен int. Это простая итерация, так как n - целое число.

(2) Ln(x): эта функция использует итерацию ряда Тейлора. Ряд, используемый в итерации, равен Σ (from int i = 0 to n) {(1 / (2 * i + 1)) * ((x - 1) / (x + 1)) ^ (2 * n + 1)}. Символ ^ обозначает степенную функцию Pow(x, n), реализованную в 1-й функции, которая использует простую итерацию.

(3) Exp(x): Эта функция снова использует итерацию серии Тейлора. Ряд, используемый в итерации, равен Σ (from int i = 0 to n) {x^i / i!}. Здесь ^ обозначает степенную функцию, но она не вычисляется путем вызова 1-й Pow(x, n) функции; вместо этого он реализован в 3-й функции одновременно с факториалом, используя d *= x / i. Я чувствовал , что мне пришлось использовать этот трюк , потому что в этой функции итерация делает еще несколько шагов относительно других функций, а факториал (i!) переполняется большую часть времени. Чтобы убедиться, что итерация не переполняется, степенная функция в этой части повторяется одновременно с факториалом. Таким образом, я преодолел переполнение.

(4) fPow(x, a): x и a оба являются двойными . Эта функция не делает ничего, кроме как просто вызывает другие три функции, реализованные выше. Основная идея этой функции зависит от некоторого исчисления: fPow(x, a) = Exp(a * Ln(x)). И теперь у меня есть все функции iPow, Ln и Exp с итерацией.

n.b. Я использовал constant MAX_DELTA_DOUBLE, чтобы решить, на каком шаге остановить итерацию. Я установил его на 1.0E-15, что кажется разумным для двойников. Таким образом, итерация останавливается, если (delta < MAX_DELTA_DOUBLE) Если вам нужна дополнительная точность, вы можете использовать long double и уменьшить постоянное значение для MAX_DELTA_DOUBLE, например, до 1.0E-18 (1,0E-18 будет минимальным).

Вот код, который работает для меня.

#define MAX_DELTA_DOUBLE 1.0E-15
#define EULERS_NUMBER 2.718281828459045

double MathAbs_Double (double x) {
    return ((x >= 0) ? x : -x);
}

int MathAbs_Int (int x) {
    return ((x >= 0) ? x : -x);
}

double MathPow_Double_Int(double x, int n) {
    double ret;
    if ((x == 1.0) || (n == 1)) {
        ret = x;
    } else if (n < 0) {
        ret = 1.0 / MathPow_Double_Int(x, -n);
    } else {
        ret = 1.0;
        while (n--) {
            ret *= x;
        }
    }
    return (ret);
}

double MathLn_Double(double x) {
    double ret = 0.0, d;
    if (x > 0) {
        int n = 0;
        do {
            int a = 2 * n + 1;
            d = (1.0 / a) * MathPow_Double_Int((x - 1) / (x + 1), a);
            ret += d;
            n++;
        } while (MathAbs_Double(d) > MAX_DELTA_DOUBLE);
    } else {
        printf("\nerror: x < 0 in ln(x)\n");
        exit(-1);
    }
    return (ret * 2);
}

double MathExp_Double(double x) {
    double ret;
    if (x == 1.0) {
        ret = EULERS_NUMBER;
    } else if (x < 0) {
        ret = 1.0 / MathExp_Double(-x);
    } else {
        int n = 2;
        double d;
        ret = 1.0 + x;
        do {
            d = x;
            for (int i = 2; i <= n; i++) {
                d *= x / i;
            }
            ret += d;
            n++;
        } while (d > MAX_DELTA_DOUBLE);
    }
    return (ret);
}

double MathPow_Double_Double(double x, double a) {
    double ret;
    if ((x == 1.0) || (a == 1.0)) {
        ret = x;
    } else if (a < 0) {
        ret = 1.0 / MathPow_Double_Double(x, -a);
    } else {
        ret = MathExp_Double(a * MathLn_Double(x));
    }
    return (ret);
}
1 голос
/ 16 декабря 2015

Вы можете найти функцию pow следующим образом:

static double pows (double p_nombre, double p_puissance)
{
    double nombre   = p_nombre;
    double i=0;
    for(i=0; i < (p_puissance-1);i++){
          nombre = nombre * p_nombre;
       }
    return (nombre);
}

Вы можете найти функцию floor следующим образом:

static double floors(double p_nomber)
{
    double x =  p_nomber;
    long partent = (long) x; 

    if (x<0)
    {
        return (partent-1);
    }
    else
    {
        return (partent);
    }
}

С наилучшими пожеланиями

1 голос
/ 21 мая 2010

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

Вы можете рассчитать корни до желаемого уровня точности, используя метод Ньютона. Ознакомьтесь с статьей в википедии об алгоритме .

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