Приблизительный пример

Я хотел бы аппроксимировать функцию ex.

Можно ли сделать это, используя подход на основе нескольких сплайнов? то есть между x1 и x2, тогда

y1 = a1x + b1, между x2 и x 3,

тогда

y2 = a2x + b2

так далее

Это для выделенного оборудования fpga, а не для процессора общего назначения. Таким образом, мне нужно создать функцию самостоятельно. Точность волнует гораздо меньше. Кроме того, я не могу позволить себе более одной схемы умножения и/или нескольких сдвигов/сумматоров. Также я хочу что-то намного меньше, чем функция CORDIC, на самом деле размер имеет решающее значение.


person trican    schedule 08.08.2011    source источник
comment
В каком диапазоне значений x вы планируете аппроксимировать это?   -  person Cassidy Laidlaw    schedule 08.08.2011
comment
Ответ по умолчанию: степенной ряд   -  person user786653    schedule 08.08.2011
comment
У вас есть функция exp() в стандарте С++. Почему вы избегаете его использования? Обычно у него хорошая скорость.   -  person George Gaál    schedule 08.08.2011
comment
Рекурсивные приближения не подходят для моего приложения. Потенциальный максимальный диапазон составляет 0-4095, но его можно масштабировать до меньшего значения. Я предполагаю, что мне нужно от 4 до 6 бит точности   -  person trican    schedule 08.08.2011
comment
Мое приложение на самом деле не C или C++, это специальное оборудование, поэтому я сам запускаю эту функцию. Функция питания хороша, но я бы предпочел что-то с меньшим количеством операций.   -  person trican    schedule 08.08.2011
comment
@ user786653: Определенно не силовая серия. Это теоретическое математическое определение, а не числовое математическое определение. На той же странице есть более практичные формулы, например. Непрерывные дроби   -  person MSalters    schedule 08.08.2011
comment
Это более-менее один. В некоторых случаях намного больше или меньше :) Извините, старый математический анекдот.   -  person Edwin Buck    schedule 08.08.2011
comment
Просто чтобы уточнить на основе оператора 0-4095: это целое число? Потому что алгоритм для целого x тривиален; просто сохраните e^1..e^2048 и умножьте на биты в x. 11 умножений в худшем случае.   -  person MSalters    schedule 08.08.2011
comment
Спасибо MSalter - да, диапазон целочисленный, но решение содержит около 10 слишком много умножений   -  person trican    schedule 08.08.2011
comment
См. математику. .stackexchange.com/questions/55830/   -  person lhf    schedule 08.08.2011
comment
@trican: re, но решение содержит примерно на 10 слишком много умножений: во-первых, это очень похоже на преждевременную оптимизацию. Во-вторых, предлагаемое вами использование сплайнов будет еще дороже. В-третьих, от 0 до 4095? exp(4095) — очень, очень большое число. Наконец, см. netlib.org/fdlibm/e_exp.c .   -  person David Hammen    schedule 09.08.2011
comment
спасибо за ответ, Дэвид, я бы хотел, чтобы это была преждевременная оптимизация, но НЕТ реализации экспоненциальных функций в языках описания оборудования, таких как Verilog или VHDL для FPGA/ASIC. Кроме того, малый размер и меньшая мощность абсолютно критичны в моем случае, и я готов обменять точность на это.   -  person trican    schedule 09.08.2011
comment
Нам действительно нужны диапазон и точность ввода и точность вывода. Q12.0 на входе дает Q400+ на выходе. Это чрезвычайно широкие сигналы, с которыми приходится иметь дело на ПЛИС.   -  person    schedule 11.08.2011
comment
@ Adam12, в моих сценариях - X будет отрицательным, что означает, что вывод ограничен между 0 и 1, поэтому я могу с этим смириться.   -  person trican    schedule 11.08.2011


Ответы (10)


Как насчет такой стратегии, которая использует формулу

ex = 2x/ln(2)

  1. Предварительный расчет 1/ln(2)
  2. Умножьте эту константу на свой аргумент (1 умножение)
  3. Используйте двоичные сдвиги, чтобы возвести 2 в целочисленную часть степени (предполагается формат exp+mantissa)
  4. Отрегулируйте на основе остатка дробной степени 2 (вероятно, второго умножения)

Я понимаю, что это не полное решение, но оно требует только одного умножения и сводит оставшуюся проблему к аппроксимации дробной степени числа 2, что должно быть проще реализовать аппаратно.

Кроме того, если ваше приложение достаточно специализировано, вы можете попытаться перестроить весь числовой код, который будет работать на вашем оборудовании, чтобы он был в системе счисления с основанием e, и реализовать ваше оборудование с плавающей запятой для также работают в базе e. Тогда вообще никакой конвертации не нужно.

person Lucas    schedule 08.08.2011
comment
спасибо Лукас - это идеально подходит для моих нужд, даже лучше, чем я мог надеяться. Большое спасибо! - person trican; 09.08.2011
comment
Рад слышать. Похоже, у вас есть интересные компромиссы в дизайне. - person Lucas; 09.08.2011
comment
@trican Есть хорошая статья о реализации этой идентичности и сокращения диапазона для достижения разумной точности для одинарной точности с плавающей запятой с использованием таблиц поиска и арифметики с фиксированной запятой: loria.fr/~detreyje/publications/DetDin_fpt_2005.pdf - person Chiggs; 03.02.2014
comment
Альтернативная ссылка на PDF: perso.citi-lab.fr/ fdedinec/recherche/publis/2005-FPT.pdf - person Lucas; 28.09.2017

Если x является целым числом, вы можете просто умножать e само на себя снова и снова.

Если x не является целым числом, вы можете рассчитать efloor(x), используя описанный выше метод, а затем умножить его на небольшую поправку. Этот поправочный член может быть легко рассчитан с использованием ряда методов аппроксимации. Один из таких способов таков:

ef1 + f(1 + f/2(1 + f/3(1 + f/4))), где f — дробная часть x

Это происходит из (оптимизированного) разложения ex по степеням, которое очень точно для небольших значений x. Если вам нужна большая точность, просто добавьте в серию больше терминов.

Этот вопрос math.stackexchange содержит несколько дополнительных умных ответов. .

РЕДАКТИРОВАТЬ: обратите внимание, что существует более быстрый способ вычисления en, который называется возведение в степень в квадрате.

person tskuzzy    schedule 08.08.2011
comment
Лучшее решение целочисленного решения — это не решение O(n). Алгоритм «разделяй и властвуй» (предварительно) вычисляет e^1, e^2, e^4, e^8 и т. д. Затем вы берете коэффициенты, соответствующие битам в x. Это O(logN). т.е. для x=255 требуется всего 8 умножений вместо 254. - person MSalters; 08.08.2011
comment
Спасибо, но я хочу свести к минимуму операции умножения, мне нужна только одна операция умножения. - person trican; 08.08.2011
comment
Но почему? Вы на самом деле наблюдаете проблемы с производительностью или это преждевременная оптимизация? - person Jonathan Grynspan; 08.08.2011
comment
@Jonathan - это не для процессора, а для выделенного оборудования. Я обновил свой вопрос выше, чтобы уточнить это. Извините за путаницу - person trican; 08.08.2011
comment
@Jonathan Потому что наличие экспоненциальной функции O (n), очевидно, приведет к плохой производительности. Преждевременная оптимизация — это неплохо на системном уровне. - person alternative; 03.06.2014
comment
Это было как раз то, что мне нужно было для целочисленной математической версии e^x. - person zawy; 09.11.2018

Во-первых, что мотивирует это приближение? Другими словами, что именно не так с прямым exp(x)?

Тем не менее, типичная реализация exp(x) заключается в следующем:

  • Найдите целое число k и число с плавающей запятой r так, чтобы x=k*log(2) + r и r находились в диапазоне от -0,5*log(2) до 0,5*log(2).
  • При таком уменьшении exp(x) равно 2k*exp(r).
  • Вычислить 2k совсем несложно.
  • Стандартные реализации exp(x) используют алгоритм типа Ремеса для получения минимаксного многочлена, приближающегося к exp(r).
  • Вы можете сделать то же самое, но использовать полином уменьшенного порядка.

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

person David Hammen    schedule 08.08.2011
comment
Remez* и чаще всего используют аппроксимацию Паде с центром на границе, чтобы ошибка в этом диапазоне была как можно меньше. Ошибка для заданного ввода x равна ограниченной ошибке, умноженной на 2^k, которая обычно уничтожает большинство этих приближений, когда ввод большой... Я «верю» в фактическую реализацию, использует как приближение паде, так и итеративное нахождение корня улучшения метод обратной функции, вычитаемой из входных данных. - person nimig18; 05.04.2017
comment
почему r должен находиться между -0.5log(2) и 0.5log(2), а не (0, 1)? - person Elinx; 23.02.2019

Или вы можете просто сделать pow(M_E, x) в C. (Некоторые платформы не имеют определенного M_E; на них вам, возможно, придется вручную указать значение e, которое приблизительно равно 2.71828182845904523536028747135266249775724709369995.)

(Как отмечает Дэвид в комментариях, exp(x) будет более эффективным, чем pow(M_E, x). Опять же, мозг еще не включился.)

Есть ли у вас пример использования, в котором вычисление ex является доказанным узким местом? Если нет, вы должны сначала кодировать для удобочитаемости; пробуйте такие виды оптимизации только в том случае, если очевидный подход слишком медленный.

person Jonathan Grynspan    schedule 08.08.2011
comment
pow(M_E, x)? Серьезно? pow(a,b) обычно реализуется как exp(b*log(a)). Использование pow - это ускорение, а не ускорение. - person David Hammen; 08.08.2011
comment
Это было своего рода моей точкой зрения - сначала напишите код правильно, затем посмотрите на его производительность. Нигде в исходном вопросе не говорится, что это вызывается миллион раз в секунду или что-то в этом роде, поэтому не сразу очевидно, что производительность будет проблемой. - person Jonathan Grynspan; 08.08.2011
comment
Независимо от производительности, exp(x) является более простым (и более переносимым!) решением, чем pow(M_E, x). Даже если бы pow() был быстрее, использование его вместо exp() было бы преждевременной оптимизацией. - person Keith Thompson; 08.08.2011
comment
Совершенно верно, и я обновил свой ответ, чтобы отразить исправление Дэвида. Можете ли вы сказать, что я еще не выпил достаточно кофе? :) - person Jonathan Grynspan; 08.08.2011

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);
}

и https://math.stackexchange.com/a/56064 (ищите аппроксимацию Паде).

person jdbertron    schedule 07.12.2012
comment
Спасибо @jdberton за добавление этого и ссылок. Подход кажется довольно интересным, однако вы уверены, что приведенный выше фрагмент кода верен? Я попробовал это для некоторых значений, и результат, кажется, даже не близок? - person trican; 12.12.2012
comment
Я думаю, что это было бы неточно для больших значений. Вы, вероятно, можете найти лучшую аппроксимацию Паде с некоторой работой, чтобы получить лучший диапазон. Это работает для меня, потому что мне не нужно ничего точного. - person jdbertron; 20.03.2013
comment
Метод Шраудольф идеален. Я не думаю, что это может стать быстрее, если точность приемлема. В своей статье он определяет, что средняя относительная ошибка составляет около 4%. Источник: nic.schraudolph.org/pubs/Schraudolph99.pdf - person Gigo; 05.05.2016
comment
Вот более современная реализация метода Шраудольфа, использующая одноточечное число с плавающей запятой вместо двойного (что является пустой тратой времени, поскольку записываются только старшие 32 бита двойного числа). machinedlearnings.com/2011/06/ - person Mark Lakata; 28.07.2016

Это не запрошенная вами гладкая сплайн-интерполяция, но она эффективна в вычислительном отношении:

float expf_fast(float x) {
   union { float f; int i; } y;
   y.i = (int)(x * 0xB5645F + 0x3F7893F5);
   return (y.f);
}

Вывод графика image

person nimig18    schedule 05.04.2017

Для аппаратного обеспечения у меня есть отличное решение для вас, ЕСЛИ вам нужно, чтобы оно было точным на уровне битов. (В противном случае просто сделайте приближение, как указано выше). Тождество exp(x) = ch(x) + sh(x), гиперболический синус и косинус. Загвоздка в том, что гиперболические синус и косинус можно вычислить с помощью метода CORIC, и, что лучше всего, они являются одной из функций FAST CORDIC, то есть они выглядят почти как умножение, а не почти как деление!

Это означает, что для площади множителя массива вы можете вычислить показатель степени с произвольной точностью всего за 2 цикла!

Посмотрите метод CORDIC - он УДИВИТЕЛЬНЫЙ для аппаратной реализации.

Еще один аппаратный подход использует небольшую таблицу в сочетании с формулой, упомянутой другими: exp(x + y) = exp(x) * exp(y). Вы можете разбить число на небольшие битовые поля — скажем, по 4 или 8 бит за раз — и просто найти показатель степени для этого битового поля. Вероятно, эффективен только для узких вычислений, но это другой подход.

person user2465201    schedule 29.09.2017

Wolfram предлагает несколько хороших способов аппроксимации с точки зрения серий и т. д.:

Страница Википедии в Taylor Series также показывает пример расширения ex около 0:

person aioobe    schedule 08.08.2011
comment
Альтернативные представления: e^x=z^x для e=z :D - person MSalters; 08.08.2011

Конечно, это возможно". Есть несколько проблем.

  1. Каковы ваши требования к точности?

  2. Готовы ли вы использовать сплайны более высокого порядка?

  3. Сколько памяти вы готовы потратить на это? Линейная функция на достаточно малых интервалах будет аппроксимировать экспоненциальную функцию с любой необходимой степенью точности, но для этого может потребоваться ОЧЕНЬ маленький интервал.

Редактировать:

Учитывая предоставленную дополнительную информацию, я провел быстрый тест. Уменьшение диапазона всегда можно использовать для экспоненциальной функции. Таким образом, если я хочу вычислить exp(x) для ЛЮБОГО x, я могу переписать задачу в виде...

y = exp(xi + xf) = exp(xi)*exp(xf)

где xi — целая часть x, а xf — дробная часть. Целая часть проста. Вычислите xi в двоичной форме, затем повторные возведения в квадрат и умножения позволят вам вычислить exp(xi) за относительно небольшое количество операций. (Другие приемы, использование степеней двойки и других интервалов могут дать вам еще больше скорости для жаждущих скорости.)

Теперь осталось только вычислить exp(xf). Можем ли мы использовать сплайн с линейными сегментами для вычисления exp(xf) на интервале [0,1] всего с 4 линейными сегментами с точностью до 0,005?

Этот последний вопрос решается с помощью функции, которую я написал несколько лет назад, которая будет аппроксимировать функцию сплайном заданного порядка в пределах фиксированного допуска на максимальную ошибку. Этот код требовал 8 сегментов в интервале [0,1] для достижения требуемого допуска с помощью кусочно-линейной сплайн-функции. Если бы я решил еще уменьшить интервал до [0,0,5], я бы теперь мог достичь предписанного допуска.

Итак, ответ прост. Если вы хотите уменьшить диапазон, чтобы уменьшить x до интервала [0,0,5], затем выполните соответствующие вычисления, тогда да, вы можете достичь требуемой точности с помощью линейного сплайна в 4 сегментах.

В конце концов, вам всегда будет лучше использовать жестко закодированную экспоненциальную функцию. Все упомянутые выше операции, безусловно, будут медленнее, чем то, что предоставит ваш компилятор, ЕСЛИ доступно exp(x).

person Community    schedule 08.08.2011
comment
большое спасибо за подробный ответ. При дальнейшем размышлении я могу допустить гораздо более высокие пределы погрешности, вероятно, до 0,05, а может быть, даже 0,1. Раньше я использовал сплайны с уменьшением диапазона для других функций, но в этом случае я думаю, что ответ Лукаса выше даже больше подходит для более низких требований к точности. Также ключевым моментом является то, что в аппаратном компиляторе НЕТ прямой реализации экспоненциальной функции. то есть я не работаю на процессоре - person trican; 09.08.2011

Это не подходит для пользовательских FPGA, но стоит упомянуть.

http://www.machinedlearnings.com/2011/06/fast-closed-logarithm-exponential.html

И исходный код:

https://code.google.com/archive/p/fastприблизительно/downloads

«Более быстрая» реализация включает только 3 шага (умножение, добавление, преобразование float в int) и окончательное приведение обратно к float. По моему опыту, точность составляет 2%, чего может быть достаточно, если вас не волнует фактическое значение, но вы используете значение в итерации максимизации логарифмического правдоподобия.

person Mark Lakata    schedule 27.07.2016