Я делаю вычисления в PHP с помощью bcmath
и нужно поднять e
дробным показателем. К несчастью, bcpow()
принимает только целочисленные показатели. Показатель степени, как правило, имеет более высокую точность, чем допускает метод с плавающей точкой, поэтому обычные арифметические функции его не обрезают.
Например:
$e = exp(1);
$pow = "0.000000000000000000108420217248550443400745280086994171142578125";
$result = bcpow($e, $pow);
Результат "1"
с ошибкой «bc math warning: ненулевая шкала в экспоненте».
Есть ли другая функция, которую я могу использовать вместо bcpow()
?
Лучше всего использовать расширение серии Taylor. Как вы заметили, PHP bcpow ограничен возведением в целочисленное возведение в степень.
Итак, что вы можете сделать, это свернуть свою собственную факториальную функцию bc и использовать страницу вики для реализации расширения экспоненциальной функции ряда Тейлора.
function bcfac($num) {
if ($num==0) return 1;
$result = '1';
for ( ; $num > 0; $num--)
$result = bcmul($result,$num);
return $result;
}
$mysum = '0';
for ($i=0; $i<300; $i++) {
$mysum = bcadd($mysum, bcdiv(bcpow($pow,$i), bcfac($i)) );
}
print $mysum;
Очевидно, что $i<300
является приближением для бесконечности … Вы можете изменить его в соответствии с вашими требованиями к производительности.
С $i=20
, Я получил
1,00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616
Это утешительно, так как этот маленький показатель степени должен давать что-то действительно близкое к 1,0.
Старый вопрос, но люди все равно могут быть заинтересованы.
Таким образом, Кевин получил правильную идею с полиномом Тейлора, но когда вы напрямую выводите свой алгоритм из него, вы можете столкнуться с проблемами, в основном ваш код замедляется для длинных строк ввода при использовании больших значений отсечения для $ i.
Вот почему:
На каждом этапе, под которым я подразумеваю каждый новый $ i, код вызывает bcfac ($ i). Каждый раз, когда вызывается bcfac, он выполняет вычисления $ i-1. И $ i идет до 299 … это почти 45000 операций! Не ваши быстрые и простые операции с плавающей запятой, а медленные BC-string-операции — если вы установите bcscale (100), ваш bcmul должен обрабатывать до 10000 пар символов!
Также bcpow замедляется с увеличением $ i. Не так много, как bcfac, потому что он вероятно использует что-то похожее на метод квадрата и умножения, но все равно что-то добавляет.
В целом требуемое время растет квадратично с вычисленным количеством полиномиальных членов.
Так что делать?
Вот подсказка:
Всякий раз, когда вы работаете с полиномами, особенно с полиномами Тейлора, используйте метод Хорнера.
Он преобразует это: exp (x) = x ^ 0/0! + х ^ 1/1! + х ^ 2/2! + х ^ 3/3! + …
…в это: exp (x) = (((…) * x / 3 + 1) * x / 2 + 1) * x / 1 + 1
И вдруг вам вообще не нужны никакие полномочия или факториалы!
function bc_exp($number) {
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $number), 1);
return $result;
}
Для каждого шага требуется всего 3 bc-операции, независимо от того, что такое $ i.
При начальном значении $ i = 299 (для вычисления exp с той же точностью, что и код Кевина) нам теперь нужно всего 897 bc-операций, по сравнению с более чем 45000.
Даже используя 30 в качестве отсечения вместо 300, нам теперь нужно всего лишь 87 bc-операций, в то время как другому коду все еще нужны 822 только для факториалов.
Метод Хорнера спасает день снова!
Некоторые другие мысли:
1) Код Кевина, вероятно, завершится с ошибкой input = «0», в зависимости от того, как bcmath обрабатывает ошибки, потому что код пытается bcpow (0,0) на первом шаге ($ i = 0).
2) Большие показатели требуют более длинные полиномы и, следовательно, больше итераций, например, bc_exp (300) даст неправильный ответ, даже если $ i = 299, а что-то вроде bc_exp (3) будет работать нормально.
Каждый член добавляет x ^ n / n! к результату, поэтому этот термин должен стать маленьким, прежде чем полином может начать сходиться. Теперь сравните два последовательных термина:
( x^(n+1)/(n+1)! ) / ( x^n/n! ) = x/n
Каждое слагаемое больше, чем предыдущее, на коэффициент x / n (который мы использовали с помощью метода Хорнера), поэтому для x ^ (n + 1) / (n + 1)! чтобы получить маленький х / п, нужно также получить маленький, что имеет место только в том случае, когда n> х.
Inconclusio: до тех пор, пока количество итераций меньше входного значения, результат будет отличаться. Только когда вы добавляете шаги до тех пор, пока количество итераций не станет больше, чем ввод, алгоритм начинает медленно сходиться.
Чтобы достичь результатов, которые могут удовлетворить кого-то, кто желает использовать bcmath, ваш $ i должен быть значительно больше, чем ваш $. И это огромная проблема, когда вы пытаетесь вычислить такие вещи, как е ^ 346674567801
Решение состоит в том, чтобы разделить входные данные на целочисленную часть и дробную часть.
Затем используйте bcpow для целочисленной части и bc_exp для дробной части, которая теперь сходится от начального значения, поскольку дробная часть меньше 1. В конце умножьте результаты.
e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart)
Вы могли бы даже реализовать это непосредственно в коде выше:
function bc_exp2($number) {
$parts = explode (".", $number);
$fracpart = "0.".$parts[1];
$result = 1;
for ($i=299; $i>0; $i--)
$result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1);
$result = bcmul(bcpow(exp(1), $parts[0]), $result);
return $result;
}
Обратите внимание, что exp (1) дает вам число с плавающей запятой, которое, вероятно, не удовлетворит ваши потребности как пользователя bcmath. Возможно, вы захотите использовать более точное значение для e в соответствии с настройкой bcscale.
3) Говоря о количестве итераций: 300 будет излишним в большинстве ситуаций, в то время как в некоторых других этого может быть даже недостаточно. Алгоритм, который берет ваши bcscale и $ number и вычисляет количество необходимых итераций, было бы неплохо. Alraedy получил некоторые идеи, связанные с log (n!), Но пока ничего конкретного.
4) Чтобы использовать этот метод с произвольной базой, вы можете использовать ^ x = e ^ (x * ln (a)).
Возможно, вы захотите разделить x на его intpart и fracpart перед использованием bc_exp (вместо того, чтобы делать это внутри bc_exp2), чтобы избежать ненужных вызовов функций.
function bc_pow2($base,$exponent) {
$parts = explode (".", $exponent);
if ($parts[1] == 0){
$result = bcpow($base,$parts[0]);
else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]);
return result;
}
Теперь нам нужно только запрограммировать bc_ln. Мы можем использовать ту же стратегию, что и выше:
Возьмем полином Тейлора функции натурального логарифма. (поскольку ln (0) не определено, вместо этого возьмите 1 в качестве точки развития)
Используйте метод Хорнера, чтобы резко улучшить производительность.
Превратите результат в цикл bc-операций.
Также используйте ln (x) = -ln (1 / x) при обработке x> 1, чтобы гарантировать сходимость.