计算机如何计算指数函数

计算机是如何计算指数函数 e x e^{x} ex 的呢?

本文并不是为了探究各种编程语言中数学计算函数库对 e x e^x ex 函数的具体实现,而是提供一种计算思路,来帮助理解 e x e^x ex 函数的计算原理,并使用 C 语言来实现这个算法。

计算原理

泰勒展开

函数 exp ⁡ ( x ) = e x \exp{(x)} = e^x exp(x)=ex 的图像如下所示:

在这里插入图片描述

与计算对数函数 log ⁡ x \log{x} logx 相似,指数函数的计算也是对 x x x 进行处理后,使用泰勒公式进行展开计算。

指数函数 e x e^x ex 的泰勒展开如下:
e x = ∑ n = 0 n = ∞ 1 n ! x n = 1 + x + 1 2 ! x 2 + 1 3 ! x 3 + 1 4 ! x 4 + ⋅ ⋅ ⋅ + 1 n ! x n ( x → 0 , n → ∞ ) (1) e^x = \sum_{n = 0}^{n = \infty} \frac{1}{n!} x^n = 1 + x + \frac{1}{2!} x^2 + \frac{1}{3!} x^3 + \frac{1}{4!} x^4 +···+ \frac{1}{n!} x^n \quad(x \rightarrow 0, n\rightarrow \infty) \tag{1} ex=n=0n=n!1xn=1+x+2!1x2+3!1x3+4!1x4+⋅⋅⋅+n!1xn(x0n)(1)
e x e^x ex 进行展开也是有条件的,那便是要求 x → 0 x \rightarrow 0 x0 ,即以上的展开式只对 x x x 在 0 点附近有效。

同时,展开式的精度和 x x x 与 0 点的距离和展开的项数有关,如下:

在这里插入图片描述

以上是对 e x e^x ex 分别进行两项、四项、六项展开的图像。各自的误差如下:

在这里插入图片描述

可以看出,在相同的展开项数下, x x x 越靠近 0 点,误差越小; x x x 固定时,展开项数越多,误差越小。

参数归约化

为了能够更加精确计算出更大范围的 x x x 值的 e x e^x ex 结果,首先要对原式进行处理。

我们知道,在计算机中,实数使用 IEEE 浮点数来表示,那么计算机其实更加善于计算 2 x 2^x 2x,只要对浮点数的阶码进行处理就能计算得出 2 x 2^x 2x 的结果。

那么我们就要想办法,将以 e e e 为底的指数函数 e x e^x ex 转化为以 2 为底的指数函数 2 x 2^x 2x的计算,至少要部分转化。

我们知道:
e = 2 log ⁡ 2 e (2) e = 2^{\log_2{e}} \tag{2} e=2log2e(2)
于是:
e x = ( 2 log ⁡ 2 e ) x = 2 x log ⁡ 2 e (3) e^x = (2^{\log_2{e}})^x = 2^{x\log_2{e}} \tag{3} ex=(2log2e)x=2xlog2e(3)
其中 log ⁡ 2 e ≈ 1.442695 \log_2e \approx 1.442695 log2e1.442695 为常数,那么我们就成功地将计算 e x e^x ex 的问题转换为计算 2 x log ⁡ 2 e 2^{x\log_2e} 2xlog2e 的问题了。

t = x log ⁡ 2 e t = x\log_2{e} t=xlog2e,则:
e x = 2 x log ⁡ 2 e = 2 t , ( t = x log ⁡ 2 e ) (4) e^x = 2^{x\log_2{e}} = 2^{t}, \qquad (t= x^{\log_2{e}}) \tag{4} ex=2xlog2e=2t,(t=xlog2e)(4)
但是问题又来了,通过构造阶码的形式来计算 2 的次幂,只能计算 2 的整数次幂,而 t t t 为实数,那该怎么办呢?

这时要利用指数函数的一个性质:
a b + c = a b ⋅ a c (5) a^{b+c} = a^b \cdot a^c \tag{5} ab+c=abac(5)
我们将 t t t 分为整数部分 m m m 与 小数部分 n n n,则:
令: t = [ t ] + ( t − [ t ] ) = m + n , ( m = [ t ] , n = t − [ t ] ) 则: 2 t = 2 m + n = 2 m ⋅ 2 n (6) 令:t = [t] + (t-[t]) = m + n, (m = [t], n = t-[t]) \\ 则:2^{t} = 2^{m+n} = 2^{m} \cdot 2^{n} \tag{6} 令:t=[t]+(t[t])=m+n,(m=[t],n=t[t])则:2t=2m+n=2m2n(6)
对于整数次幂 2 m 2^m 2m 可通过构造阶码的形式来实现,对于小数次幂 2 n 2^n 2n ,则可对其进行再次转化为 e x e^x ex 的形式,对其进行展开:

因为 2 = e ln ⁡ 2 2 = e^{\ln2} 2=eln2 ,所以:
2 n = ( e ln ⁡ 2 ) n = e n ln ⁡ 2 (7) 2^n = (e^{\ln2})^n = e^{n\ln2} \tag{7} 2n=(eln2)n=enln2(7)
因为 n ∈ ( − 1 , 1 ) , ln ⁡ 2 ≈ 0.693147 n \in (-1,1),\ln2 \approx 0.693147 n(1,1),ln20.693147 ,所以 n ln ⁡ 2 ∈ ( − 0.693147 , + 0.693147 ) n\ln2 \in (-0.693147, +0.693147) nln2(0.693147,+0.693147) ,已经足够接近 0 点,所以可以对其使用泰勒展开进行计算。

k = n ln ⁡ 2 , k ∈ ( − 0.693147 , + 0.693147 ) k = n\ln2,\quad k \in (-0.693147, +0.693147) k=nln2,k(0.693147,+0.693147),则:
2 n = e k , ( k = n ln ⁡ 2 ) (8) 2^n = e^k, \quad (k = n\ln2) \tag{8} 2n=ek,(k=nln2)(8)
那么:
e x = 2 x log ⁡ 2 e = 2 t = 2 m + n ( t = x l o g 2 e , m = [ t ] , n = t − [ t ] ) = 2 m ⋅ 2 n = 2 m ⋅ e n ln ⁡ 2 = 2 m ⋅ e k ( k = n ln ⁡ 2 , n ∈ ( − ln ⁡ 2 , + ln ⁡ 2 ) ) \begin{align} e^x &= 2^{x\log_2{e}} \\ & = 2^t = 2^{m+n} \qquad (t = xlog_2{e}, m = [t], n = t - [t]) \\ & = 2^m \cdot 2^n \\ & = 2^m \cdot e^{n\ln2} = 2^m \cdot e^k \qquad (k = n\ln2, n \in (-\ln2, +\ln2)) \end{align} ex=2xlog2e=2t=2m+n(t=xlog2e,m=[t],n=t[t])=2m2n=2menln2=2mek(k=nln2,n(ln2,+ln2))
对于 e k e^k ek,根据 (1) 式:
e k = ∑ n = 0 ∞ 1 n ! k n = 1 + k + 1 2 ! k 2 + 1 3 ! k 3 + 1 4 ! k 4 + 1 5 ! k 5 + o ( k 5 ) e^k = \sum_{n = 0}^{\infty} \frac{1}{n!} k^n = 1 + k + \frac{1}{2!}k^2 + \frac{1}{3!}k^3 + \frac{1}{4!}k^4 + \frac{1}{5!}k^5 + o(k^5) ek=n=0n!1kn=1+k+2!1k2+3!1k3+4!1k4+5!1k5+o(k5)

实现思路

先将 x x x 乘以 log ⁡ 2 e ≈ 1.442695 \log_2{e} \approx 1.442695 log2e1.442695 得到 t t t

float x = 3.14;
float t = x * 1.442695;    // 1.442695 = log2(e)

分别取得 t t t 的整数部分 m m m 和小数部分 n n n

m = (round)((int)(t * 2.0) / 2);
float n = t - int(t);

通过 m m m 来构造 2 m 2^m 2m

m = (m + 127) << 23; 

然后将 n n n 乘以 ln ⁡ 2 \ln2 ln2 得到 k k k

float k = n * 0.693147;    // 0.693147 = ln(2)

再使用泰勒公式将其展开即可。

代码实现

源码

#include <stdio.h>
#include <math.h>

float my_exp(float x){
	float t = x * 1.442695;
	int m = 0;
	float n = 0;
	m = (round)((int)(t * 2.0) / 2);
	n = t - m;

	m = (m + 127) << 23; 
	n = n * 0.693147;
	float tmp_n = n;
	float j = 1;
	float result = 1 + n;
	for (int i = 2; i < 7; i++){
		j = j * i;
		tmp_n = tmp_n * n;
		result += tmp_n / j;
	}
	return *(float *)&m * result;
}

int main(int argc, char *argv[]) {
	
	my_exp(3.14);
	for (int i = -10; i <= 30; i++){
		float x = i * 0.1;
		printf("x = %2.6f, my log(x) = %2.6f, log(x) = %2.6f, err = %2.6f\n", x, my_exp(x), exp(x), fabs(my_exp(x) - exp(x)));
	}
	return 0;
}

运行结果

在这里插入图片描述

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值