Приблизительно е ^ х
Я хотел бы приблизить функцию ех.
Можно ли сделать это, используя подход с несколькими сплайнами? то есть между х1 и х2, то
y1 = a1 x + b1, между x2 и x3,
затем
y2 = a2 x + b2
так далее
Это для выделенного оборудования fpga, а не процессора общего назначения. Поэтому мне нужно создать эту функцию самостоятельно. Точность гораздо менее важна. Более того, я не могу позволить себе более одной схемы умножения и / или нескольких смен / сумматоров. Также я хочу что-то намного меньшее, чем CORDIC-функция, на самом деле размер имеет решающее значение.
10 ответов
Как насчет такой стратегии, которая использует формулу
е х = 2 х / лн (2)
- предвычислять
1/ln(2)
- Умножьте эту константу на ваш аргумент (1 умножение)
- Используйте двоичные сдвиги, чтобы поднять 2 до целой части степени (предполагается, что формат exp + mantissa)
- Отрегулируйте на основе дробного остатка степени 2 (вероятно, второго умножения)
Я понимаю, что это не полное решение, но оно требует только одного умножения и сводит оставшуюся проблему к приблизительной дробной степени 2, что должно быть легче реализовать в аппаратном обеспечении.
Кроме того, если ваше приложение достаточно специализировано, вы можете попытаться восстановить весь числовой код, который будет работать на вашем оборудовании, в базовой системе счисления и внедрить ваше оборудование с плавающей запятой для работы также в базе e. Тогда никакого преобразования не требуется вообще.
Если x
является целым числом, вы можете просто умножить e
само по себе снова и снова.
Если x
не является целым числом, вы можете вычислить efloor (x), используя вышеупомянутый метод, а затем умножить на небольшой поправочный член. Этот поправочный член может быть легко рассчитан с использованием ряда методов аппроксимации. Один из таких способов:
еф ≈
1 + f(1 + f/2(1 + f/3(1 + f/4)))
где f - дробная часть x
Это происходит из-за (оптимизированного) расширения ряда мощности ex, которое очень точно для малых значений x
, Если вам нужно больше точности, просто добавьте больше терминов к серии.
Этот вопрос math.stackexchange содержит несколько дополнительных умных ответов.
РЕДАКТИРОВАТЬ: Обратите внимание, что существует более быстрый способ вычисления en, называемый возведением в степень путем возведения в квадрат.
Прежде всего, что мотивирует это приближение? Другими словами, что именно не так с простым exp(x)
?
Тем не менее, типичная реализация exp(x)
это к
- Найти целое число
k
и число с плавающей запятойr
такой, чтоx=k*log(2) + r
а такжеr
находится между -0,5*log(2) и 0,5*log(2). - С этим сокращением,
exp(x)
это 2к*exp(r)
, - Расчет 2k совсем несложно.
- Стандартные реализации
exp(x)
использовать алгоритм типа Ремеса, чтобы придумать минимаксный многочлен, который приближаетсяexp(r)
, - Вы можете сделать то же самое, но использовать полином уменьшенного порядка.
Вот кикер: независимо от того, что вы делаете, шансы очень высоки, что ваша функция будет намного, намного медленнее, чем просто вызов exp()
, Большая часть функциональности exp()
реализован в математическом сопроцессоре вашего компьютера. Реализация этой функциональности в программном обеспечении, даже с уменьшенной точностью, будет на порядок медленнее, чем просто использование exp()
,
Что касается оборудования, у меня есть отличное решение для вас, если вам нужно, чтобы оно было точным на уровне битов. (Иначе просто сделайте приближение как выше). Тождество: exp(x) = cosh(x) + sinh(x), гиперболический синус и косинус. Суть в том, что гиперболический синус и косинус могут быть вычислены с использованием техники CORIC, и, что лучше всего, они являются одной из функций FAST CORDIC, то есть они выглядят почти как умножение, а не как деление!
Это означает, что для области множителя массива вы можете вычислить показатель степени с произвольной точностью всего за 2 цикла!
Посмотрите на метод CORDIC - это удивительно для аппаратной реализации.
Еще один аппаратный подход заключается в использовании небольшой таблицы в сочетании с формулой, упомянутой другими: exp(x + y) = exp(x) * exp(y). Вы можете разбить число на небольшие битовые поля - скажем, 4 или 8 бит за раз - и просто найти показатель степени для этого битового поля. Вероятно, эффективен только для узких вычислений, но это другой подход.
Или вы могли бы просто сделать pow(M_E, x)
в C. (Некоторые платформы не имеют M_E
определены; на тех, вам, возможно, придется вручную указать значение е, которое примерно 2.71828182845904523536028747135266249775724709369995
.)
(Как указывает Дэвид в комментариях, exp(x)
будет более эффективным, чем pow(M_E, x)
, Опять же мозг еще не включен.)
Есть ли у вас случай, когда вычисление ex является проверенным узким местом? Если нет, вы должны сначала написать код для удобства чтения; Только попробуйте такие виды оптимизации, если очевидный подход слишком медленный.
Это не запрошенная вами гладкая сплайн-интерполяция, а ее вычислительно эффективная:
float expf_fast(float x) {
union { float f; int i; } y;
y.i = (int)(x * 0xB5645F + 0x3F7893F5);
return (y.f);
}
Выходной график
http://martin.ankerl.com/2007/02/11/optimized-exponential-functions-for-java/ с использованием метода Шраудольфа ( http://nic.schraudolph.org/pubs/Schraudolph99.pdf) в Java:
public static double exp(double val) {
final long tmp = (long) (1512775 * val) + (1072693248 - 60801);
return Double.longBitsToDouble(tmp << 32);
}
и /questions/4653410/luchshij-sposob-vstavit-metku-vremeni-v-vim/4653414#4653414 (ищите приблизительный пример Паде).
Это не подходит для пользовательских ПЛИС, но стоит упомянуть.
http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html
И исходный код:
https://code.google.com/archive/p/fastapprox/downloads
"Более быстрая" реализация включает в себя только 3 шага (умножение, добавление, преобразование float в int) и окончательное приведение обратно к float. По моему опыту, это точность на 2%, что может быть достаточно, если вы не заботитесь о фактическом значении, но используете значение в итерации максимизации правдоподобия.
Вольфрам предлагает несколько хороших способов приблизить его в терминах серий и т. Д.
На странице Википедии Taylor Series также показан пример расширения ex около 0:
Конечно, это возможно". Есть несколько вопросов.
Каковы ваши требования к точности?
Готовы ли вы использовать сплайны более высокого порядка?
Сколько памяти вы готовы потратить на это? Линейная функция через достаточно малые интервалы будет приближать экспоненциальную функцию с любой необходимой степенью точности, но для этого может потребоваться ОЧЕНЬ небольшой интервал.
Редактировать:
Учитывая предоставленную дополнительную информацию, я провел быстрый тест. Уменьшение диапазона всегда можно использовать для экспоненциальной функции. Таким образом, если я хочу вычислить exp(x) для ЛЮБОГО x, тогда я могу переписать проблему в виде...
y = exp(xi + xf) = exp(xi)*exp(xf)
где xi - целая часть x, а xf - дробная часть. Целая часть проста. Вычислите xi в двоичном виде, затем повторяющиеся квадраты и умножения позволяют вычислить exp(xi) за относительно небольшое количество операций. (Другие трюки с использованием степеней 2 и других интервалов могут дать вам еще большую скорость для голодных.)
Теперь осталось только вычислить exp(xf). Можем ли мы использовать сплайн с линейными сегментами для вычисления exp (xf) в интервале [0,1] только с 4 линейными сегментами с точностью до 0,005?
Этот последний вопрос решается с помощью функции, которую я написал несколько лет назад, которая будет аппроксимировать функцию со сплайном заданного порядка с точностью до фиксированного допуска на максимальную ошибку. Этот код требует 8 сегментов за интервал [0,1] для достижения требуемого допуска с помощью кусочно-линейной функции сплайна. Если бы я решил уменьшить интервал до [0,0,5], я мог бы теперь достичь предписанного допуска.
Так что ответ прост. Если вы хотите уменьшить диапазон, чтобы уменьшить x до интервала [0.0.5], а затем выполнить соответствующие вычисления, тогда да, вы можете достичь требуемой точности с помощью линейного сплайна в 4 сегментах.
В конце концов, вам всегда будет лучше, если использовать жестко запрограммированную экспоненциальную функцию. Все операции, упомянутые выше, будут, безусловно, медленнее, чем то, что обеспечит ваш компилятор, если доступен exp exp(x).