我想近似计算ex函数。
是否可以使用多个基于样条的方法来实现?例如在x1和x2之间,
y1 = a1x + b1,在 x2 和 x3 之间,
然后
y2 = a2x + b2
等等。
这是为专用FPGA硬件设计的,而不是通用CPU。因此,我需要自己创建该函数。精度并不是很重要。此外,我不能负担得起多个乘法电路和/或多个移位/加法器。同时,我希望它比CORDIC函数小得多,事实上大小非常关键。
我想近似计算ex函数。
是否可以使用多个基于样条的方法来实现?例如在x1和x2之间,
y1 = a1x + b1,在 x2 和 x3 之间,
然后
y2 = a2x + b2
等等。
这是为专用FPGA硬件设计的,而不是通用CPU。因此,我需要自己创建该函数。精度并不是很重要。此外,我不能负担得起多个乘法电路和/或多个移位/加法器。同时,我希望它比CORDIC函数小得多,事实上大小非常关键。
ex = 2 x/ln(2)
1/ln(2)
x
是整数,你可以一遍又一遍地将e
乘以自己。如果x
不是整数,则可以使用上述方法计算 efloor(x),然后再乘以一个小的修正项。这个修正项可以使用多种近似方法轻松地计算得出。其中一种方法如下:
ef ≈
1 + f(1 + f/2(1 + f/3(1 + f/4)))
,其中f是x
的小数部分。
这来自于(优化的)幂级数展开式,非常适用于小值的x
。如果需要更高的精度,只需在级数后面添加更多的项。
这个math.stackexchange问题包含了一些额外巧妙的答案。
编辑:注意,有一种更快速的计算 en 的方法,称为平方幂运算。
x
中位相对应的因子。这是O(logN)。例如,对于x=255,只需要进行8次乘法,而不是254次。 - MSalters首先,是什么促使这个近似值的出现?换句话说,直接使用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)
。exp(x)
实现使用Remes算法来得出一个最小极大多项式,以逼近exp(r)
。关键是:无论您做什么,很可能您的函数速度都比直接调用exp()
慢得多。大部分exp()
函数的功能都是由您计算机上的数学协处理器实现的。即使降低精度,在软件中重新实现该功能的速度也会比直接使用exp()
慢一个数量级。
x
,误差等于有界误差乘以2^k
,当输入很大时通常会破坏这些逼近的大部分...我“相信”实际实现同时采用了Pade逼近和迭代改进根查找方法的反函数减去输入。 - nimig18r
应该位于 -0.5log(2)
和 0.5log(2)
之间,而不是 (0, 1)
? - Elinx对于硬件方面,如果您需要其精度与二进制位一致,我有一个很棒的解决方案。(否则,只需像上面那样进行近似计算)。恒等式为exp(x) = cosh(x) + sinh(x),其中超越正弦和余弦可以使用CORIC技术计算,更妙的是,它们是最快的CORDIC函数之一,这意味着它们看起来几乎像乘法而不是几乎像除法!
这意味着您只需要占用大约一个阵列乘法器的面积,在仅两个周期内就可以计算任意精度的指数!
请查找CORDIC方法- 这对于硬件实现非常棒。
另外一个硬件方法是使用一个小表格结合其他人提到过的公式:exp(x + y) = exp(x) * exp(y)。您可以将数字分成小的位字段 - 每次4或8位 - 然后查找该位字段的指数。这可能只适用于窄计算,但这是另一种方法。
http://martin.ankerl.com/2007/02/11/optimized-exponential-functions-for-java/使用Schraudolph的方法(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(查找Pade逼近)。
float expf_fast(float x) {
union { float f; int i; } y;
y.i = (int)(x * 0xB5645F + 0x3F7893F5);
return (y.f);
}
图形输出
http://www.machinedlearnings.com/2011/06/fast-approximate-logarithm-exponential.html
而且源代码:
“更快”的实现只涉及3个步骤(乘法,加法,将浮点数转换为整数),最后还要进行一次浮点数到整数的转换。根据我的经验,它的准确率为2%,如果您不关心实际值而是在对数似然最大化迭代中使用该值,则可能足够。”当然是“可能的”。但有几个问题需要考虑:
你对精度有什么要求?
你是否愿意使用高阶样条?
你愿意在这方面花费多少内存?线性函数在足够小的区间内将指数函数逼近到任何所需的精度,但可能需要非常小的区间。
编辑:
根据提供的额外信息,我进行了快速测试。范围缩减总是可以用于指数函数。因此,如果我想计算任何x的exp(x),那么我可以将问题重写为...
y = exp(xi + xf) = exp(xi)*exp(xf)
其中 xi 是 x 的整数部分,xf 是小数部分。整数部分很简单。将 xi 计算成二进制形式,然后通过重复平方和乘法运算,可以在相对较少的操作次数内计算出 exp(xi)。(使用 2 的幂和其他间隔的技巧可以为追求速度的用户提供更快的速度。)
现在剩下的就是计算 exp(xf)。我们能否使用具有线性段的样条函数,在区间 [0,1] 上仅使用 4 个线性段,以精度为 0.005 计算 exp(xf)?
我几年前编写了一个函数来解决这个问题,它将近似于给定阶数的函数,使其最大误差在固定容差范围内。该代码需要在区间 [0,1] 上使用 8 个线性段才能使用分段线性样条函数达到所需的容差。如果我选择将区间进一步缩小到 [0,0.5],那么我现在可以实现所要求的容差。
因此,答案很简单。如果您愿意进行范围缩减,将 x 缩小到区间 [0.0.5],然后进行适当的计算,那么是的,您可以使用 4 个线性段的线性样条函数实现所需的精度。
最终,使用硬编码指数函数总是更好的选择。如果exp(x)可用,则上述提到的所有操作肯定比编译器提供的慢。
在C语言中,您可以使用pow(M_E, x)
来计算。 (某些平台没有定义M_E
;在这些平台上,您可能需要手动指定e的值,该值约为2.71828182845904523536028747135266249775724709369995
。)
(正如David在评论中指出的那样,exp(x)
比pow(M_E, x)
更有效率。再次强调,大脑还没有开机。)
您是否有一个使用案例,其中计算ex是已知的瓶颈?如果没有,您应该首先编写易读的代码;只有在明显的方法太慢时才尝试这些优化。
pow(M_E, x)
?真的吗?通常情况下,pow(a,b)
的实现方式是 exp(b*log(a))
。使用 pow
会拖慢速度,而不是加速。请确认是否需要继续使用 pow
。 - David Hammenexp(x)
比pow(M_E, x)
更简单(并且更便携!)。即使pow()
更快,而不是使用exp()
,诉诸于它将是过早的优化。 - Keith Thompson
exp()
函数,为什么要避免使用它呢?通常它的速度很快。 - George Gaál