Очень простое решение - использовать приличное табличное приближение. На самом деле вам не нужно много данных, если вы правильно уменьшите свои данные. exp(a)==exp(a/2)*exp(a/2)
, что означает, что вам действительно нужно рассчитать exp(x)
для 1 < x < 2
. В этом диапазоне приближение Рунга-Кутты дало бы разумные результаты с ~ 16 записями IIRC.
Аналогично, sqrt(a) == 2 * sqrt(a/4) == sqrt(4*a) / 2
, что означает, что вам нужны только записи в таблице для 1 < a < 4
. Log (a) немного сложнее: log(a) == 1 + log(a/e)
. Это довольно медленная итерация, но log (1024) составляет всего 6,9, поэтому у вас не будет много итераций.
Вы бы использовали аналогичный алгоритм "integer-first" для pow: pow(x,y)==pow(x, floor(y)) * pow(x, frac(y))
. Это работает, потому что pow(double, int)
тривиально (разделяй и властвуй).
[править] Для интегрального компонента log(a)
может быть полезно сохранить таблицу 1, e, e^2, e^3, e^4, e^5, e^6, e^7
, чтобы можно было уменьшить log(a) == n + log(a/e^n)
с помощью простого жестко заданного двоичного поиска в этой таблице. Улучшение с 7 до 3 шагов не так уж и велико, но это означает, что вам нужно делить только один раз на e^n
вместо n
раз на e
.
[править 2]
И для этого последнего log(a/e^n)
термина вы можете использовать log(a/e^n) = log((a/e^n)^8)/8
- каждая итерация выдает еще 3 бита при поиске в таблице . Это сохраняет ваш код и размер таблицы маленьким. Обычно это код для встроенных систем, и у них нет больших кешей.
[править 3]
Это все еще не умный на моей стороне. log(a) = log(2) + log(a/2)
. Вы можете просто сохранить значение с фиксированной точкой log2=0.30102999566
, сосчитать число ведущих нулей, сместить a
в диапазон, используемый для вашей таблицы поиска, и умножить это смещение (целое число) на постоянную с фиксированной точкой log2
. Может быть всего 3 инструкции.
Использование e
для шага сокращения дает просто «хорошую» константу log(e)=1.0
, но это ложная оптимизация. 0,30102999566 является такой же хорошей константой, как 1,0; оба являются 32-битными константами в фиксированной точке 10,22. Использование 2 в качестве константы для уменьшения диапазона позволяет использовать битовый сдвиг для деления.
Вы все еще получаете трюк от правки 2, log(a/2^n) = log((a/2^n)^8)/8
. По сути, это дает вам результат (a + b/8 + c/64 + d/512) * 0.30102999566
- с b, c, d в диапазоне [0,7]. a.bcd
действительно восьмеричное число. Не удивительно, так как мы использовали 8 в качестве силы. (Трюк одинаково хорошо работает с мощностью 2, 4 или 16.)
[править 4]
Все еще был открытый конец. pow(x, frac(y)
это просто pow(sqrt(x), 2 * frac(y))
, и у нас есть приличное 1/sqrt(x)
. Это дает нам гораздо более эффективный подход. Скажите frac(y)=0.101
двоичный, то есть 1/2 плюс 1/8. Тогда это означает, что x^0.101
это (x^1/2 * x^1/8)
. Но x^1/2
это просто sqrt(x)
и x^1/8
это (sqrt(sqrt(sqrt(x)))
. Сохраняя еще одну операцию, Ньютон-Рафсон NR(x)
дает нам 1/sqrt(x)
, поэтому мы вычисляем 1.0/(NR(x)*NR((NR(NR(x)))
. Мы только инвертируем конечный результат, напрямую не используем функцию sqrt.