[旧日谈]基于IEEE754的快速EXP函数估计算法

[旧日谈]基于IEEE754的快速EXP函数估计算法

前置知识 - exp函数变换

首先我们来转换一下exp函数表达式,令其更趋向于我们所需要的表达法。

我们需要求的式为 e x p ( x ) = e x s exp(x)=e^xs exp(x)=exs

我们观察此式 e x e^x ex

则有
e ln ⁡ ( a x ) = a x e^{\ln(a^x)}=a^x eln(ax)=ax

这里令a=2,则有
e ln ⁡ ( 2 ) ∗ b = 2 b e^{\ln(2)*b}=2^b eln(2)b=2b

x = ln ⁡ ( 2 ) ∗ b x=\ln(2)*b x=ln(2)b,则有:
e x = 2 x / ln ⁡ ( 2 ) e^x=2^{x/\ln(2)} ex=2x/ln(2)

记住这个推论,接下来我们会用到exp的这种表达式来进行推导。当然了这里的a取多少都行,因为我们接下来的计算需要基于IEEE 754约定,所以我们就需要在这里取2.

前置知识:IEEE 754的浮点数表达法:

IEEE 754 浮点数由三部分组成:

  • 符号位(Sign Bit)​:1 位,表示数的正负(0 为正,1 为负)。
  • 指数位(Exponent)​:若干位,表示数的指数部分。
  • 尾数位(Mantissa)​:若干位,表示数的小数部分。

一般常见的两种分别是单精度浮点数与双精度浮点数,其位数描述是这样的:

类型符号位指数位尾数位总位数
单精度浮点数182332
双精度浮点数1115264
浮点数的具体计算方式:

V a l u e = ( − 1 ) S i g n ∗ ( 1 + M a n t i s s a ) ∗ 2 E x p o n e n t − b i a s Value = (-1)^{Sign} *(1 + Mantissa)* 2^{Exponent-bias} Value=(1)Sign(1+Mantissa)2Exponentbias

前置知识:指数偏移bias

首先要知道双精度浮点数的内存上表达,内存中表达如图所示:

20250306150644

为什么要做这样一个偏移?其实这个偏移是刚好把s和e拼在了一起,当成一个完整的12bit的数字来看待,这样当我们将这个指数当成一个无符号的整数来看待时,直接加上一个溢出的值,这样就可以避免复杂 处理和判断了。

举个例子,以一个32位的单精度浮点数为例

情况1:正指数

  • 实际指数E=3
  • 存储的指数e=E+127=130
  • 二进制表示:130=10000010

情况2 :零指数

  • 实际指数E=0
  • 存储的指数e=E+127=127
  • 二进制表示:127=01111111

情况3:负指数

  • 实际指数E=-3
  • 存储的指数e=E+127=124
  • 二进制表示:124=01111100

这样我们就可以把有符号的指数转换成无符号的存储值来看待了

核心算法推导

目标表达式构造

我们需要构造浮点数使其满足:

2 x / ln ⁡ 2 = ( 1 + m ) ⋅ 2 e − 1023 ( 1 ) 2^{x/\ln2} = (1 + m) \cdot 2^{e - 1023} \quad (1) 2x/ln2=(1+m)2e1023(1)

对两边取对数得:

x ln ⁡ 2 = ( e − 1023 ) + log ⁡ 2 ( 1 + m ) ( 2 ) \frac{x}{\ln2} = (e - 1023) + \log_2(1 + m) \quad (2) ln2x=(e1023)+log2(1+m)(2)

整数分解与线性插值

将输入参数分解为整数和小数部分:

x ln ⁡ 2 = k + δ 其中 { k = ⌊ x / ln ⁡ 2 ⌋ δ ∈ [ 0 , 1 ) \frac{x}{\ln2} = k + \delta \quad \text{其中} \quad \begin{cases} k = \lfloor x/\ln2 \rfloor \\ \delta \in [0,1) \end{cases} ln2x=k+δ其中{k=x/ln2δ[0,1)

此时指数表达式可拆分为:

2 k + δ = 2 k ⋅ 2 δ 2^{k+\delta} = 2^k \cdot 2^\delta 2k+δ=2k2δ

指数部分处理
  • 整数部分:通过调整浮点数的指数位实现
  • 小数部分:通过尾数位进行线性插值近似

位操作实现

指数位设置

由公式(2)可得指数存储值:

e = k + 1023 = ⌊ x / ln ⁡ 2 ⌋ + 1023 e = k + 1023 = \lfloor x/\ln2 \rfloor + 1023 e=k+1023=x/ln2+1023

尾数位设置

将小数部分 δ \delta δ映射到尾数位:

  1. δ \delta δ的二进制小数形式直接填充52位尾数
  2. 对应内存操作:将 δ \delta δ左移52位

数学证明

一、指数位设置的严格性证明

命题

当设置浮点数指数位为:
e = ⌊ x / ln ⁡ 2 ⌋ + 1023 e = \lfloor x/\ln2 \rfloor + 1023 e=x/ln2+1023
时,该浮点数的指数部分将精确表示 2 k 2^k 2k(其中 k = ⌊ x / ln ⁡ 2 ⌋ k = \lfloor x/\ln2 \rfloor k=x/ln2

证明

根据IEEE754双精度浮点数规范:

  1. 存储的指数值需满足:
    e s t o r e d = E a c t u a l + 1023 ( E a c t u a l ∈ Z ) e_{stored} = E_{actual} + 1023 \quad (E_{actual} \in \mathbb{Z}) estored=Eactual+1023(EactualZ)
  2. 实际指数计算为:
    E a c t u a l = e s t o r e d − 1023 E_{actual} = e_{stored} - 1023 Eactual=estored1023

x ln ⁡ 2 \frac{x}{\ln2} ln2x分解为整数和小数部分:
x ln ⁡ 2 = k + δ ( k ∈ Z ,   δ ∈ [ 0 , 1 ) ) \frac{x}{\ln2} = k + \delta \quad (k \in \mathbb{Z},\ \delta \in [0,1)) ln2x=k+δ(kZ, δ[0,1))

则:
2 x / ln ⁡ 2 = 2 k ⋅ 2 δ 2^{x/\ln2} = 2^k \cdot 2^\delta 2x/ln2=2k2δ

此时:
E a c t u a l = k ⇒ e s t o r e d = k + 1023 E_{actual} = k \quad \Rightarrow \quad e_{stored} = k + 1023 Eactual=kestored=k+1023

证毕。


二、尾数位线性插值的误差分析

命题

δ \delta δ的二进制小数直接填充到尾数位,等价于用一阶泰勒展开近似:
2 δ ≈ 1 + δ ( δ ∈ [ 0 , 1 ) ) 2^\delta \approx 1 + \delta \quad (\delta \in [0,1)) 2δ1+δ(δ[0,1))

其最大相对误差为:
ϵ m a x = 2 δ − ( 1 + δ ) 2 δ ≤ 4.67 % ( 当 δ = 1 时 ) \epsilon_{max} = \frac{2^\delta - (1+\delta)}{2^\delta} \leq 4.67\% \quad (\text{当} \delta=1 \text{时}) ϵmax=2δ2δ(1+δ)4.67%(δ=1)

证明
  1. 泰勒展开分析

    • 泰勒展开式:
      2 δ = e δ ln ⁡ 2 = 1 + δ ln ⁡ 2 + ( δ ln ⁡ 2 ) 2 2 + ⋯ 2^\delta = e^{\delta \ln2} = 1 + \delta \ln2 + \frac{(\delta \ln2)^2}{2} + \cdots 2δ=eδln2=1+δln2+2(δln2)2+
    • 一阶截断:
      2 δ ≈ 1 + δ ln ⁡ 2 ( ln ⁡ 2 ≈ 0.693 ) 2^\delta \approx 1 + \delta \ln2 \quad (\ln2 \approx 0.693) 2δ1+δln2(ln20.693)
  2. 实际误差计算

    • 真实值: 2 δ 2^\delta 2δ
    • 近似值: 1 + δ 1 + \delta 1+δ
    • 相对误差:
      ϵ = ∣ 2 δ − ( 1 + δ ) 2 δ ∣ \epsilon = \left| \frac{2^\delta - (1+\delta)}{2^\delta} \right| ϵ= 2δ2δ(1+δ)
    δ真实值近似值相对误差
    0.01.0001.0000.00%
    0.51.4141.5006.07%
    1.02.0002.0000.00%
  3. 误差上界推导
    令函数:
    f ( δ ) = 2 δ − ( 1 + δ ) f(\delta) = 2^\delta - (1+\delta) f(δ)=2δ(1+δ)
    求导得极值点:
    f ′ ( δ ) = 2 δ ln ⁡ 2 − 1 = 0 ⇒ δ = log ⁡ 2 ( 1 ln ⁡ 2 ) ≈ 0.528 f'(\delta) = 2^\delta \ln2 - 1 = 0 \quad \Rightarrow \quad \delta = \log_2\left(\frac{1}{\ln2}\right) \approx 0.528 f(δ)=2δln21=0δ=log2(ln21)0.528
    此时最大误差:
    ϵ m a x ≈ 2 0.528 − 1.528 2 0.528 ≈ 4.67 % \epsilon_{max} \approx \frac{2^{0.528} - 1.528}{2^{0.528}} \approx 4.67\% ϵmax20.52820.5281.5284.67%


三、内存位操作的数学等价性

命题

δ \delta δ左移52位的操作等价于构造尾数:
m = δ − ⌊ 2 52 δ ⌋ 2 52 m = \delta - \frac{\lfloor 2^{52} \delta \rfloor}{2^{52}} m=δ252252δ

证明
  1. 二进制小数表示
    任何 δ ∈ [ 0 , 1 ) \delta \in [0,1) δ[0,1)可表示为:
    δ = ∑ i = 1 52 b i 2 − i + ϵ ( b i ∈ { 0 , 1 } ,   ϵ < 2 − 52 ) \delta = \sum_{i=1}^{52} b_i 2^{-i} + \epsilon \quad (b_i \in \{0,1\},\ \epsilon < 2^{-52}) δ=i=152bi2i+ϵ(bi{0,1}, ϵ<252)

  2. 左移操作解析

    • 左移52位相当于计算:
      δ × 2 52 = 整数部分 + 小数部分 \delta \times 2^{52} = \text{整数部分} + \text{小数部分} δ×252=整数部分+小数部分
    • 取整数部分:
      ⌊ δ × 2 52 ⌋ = ∑ i = 1 52 b i 2 52 − i \lfloor \delta \times 2^{52} \rfloor = \sum_{i=1}^{52} b_i 2^{52-i} δ×252=i=152bi252i
  3. 尾数构造
    IEEE754尾数的实际存储值为:
    m = ⌊ δ × 2 52 ⌋ 2 52 m = \frac{\lfloor \delta \times 2^{52} \rfloor}{2^{52}} m=252δ×252
    因此:
    1 + m = 1 + δ − ϵ ( ϵ < 2 − 52 ) 1 + m = 1 + \delta - \epsilon \quad (\epsilon < 2^{-52}) 1+m=1+δϵ(ϵ<252)


四、参数c的最优性证明

命题

当选择修正参数:
c = 2 20 ln ⁡ 2 ( ln ⁡ ( ln ⁡ 2 ) + 1 ) ≈ 60 , 801 c = \frac{2^{20}}{\ln2} \left( \ln(\ln2) + 1 \right) \approx 60,801 c=ln2220(ln(ln2)+1)60,801
时,可最小化近似误差的均方根(RMS)

证明
  1. 误差函数定义
    定义相对误差函数:
    r ( y ) = EXP ( y ) − e y e y r(y) = \frac{\text{EXP}(y) - e^y}{e^y} r(y)=eyEXP(y)ey

  2. 积分均方误差
    RMS = 1 y m a x − y m i n ∫ y m i n y m a x r ( y ) 2 d y \text{RMS} = \sqrt{\frac{1}{y_{max}-y_{min}} \int_{y_{min}}^{y_{max}} r(y)^2 dy} RMS=ymaxymin1yminymaxr(y)2dy

  3. Lambert W函数解
    通过求解误差积分的最小值,得到方程:
    W ( − e γ ln ⁡ 2 2 ) = − ( γ + 1 ) W\left(-\frac{e^{\gamma} \ln2}{2}\right) = -(\gamma + 1) W(2eγln2)=(γ+1)
    其中 γ = c ln ⁡ 2 / 2 20 \gamma = c \ln2 / 2^{20} γ=cln2/220 W W W为Lambert W函数。

  4. 数值解
    使用迭代法求得:
    γ ≈ 0.0411 ⇒ c ≈ 60 , 801 \gamma \approx 0.0411 \quad \Rightarrow \quad c \approx 60,801 γ0.0411c60,801


结论

通过上述数学证明,我们严格验证了:

  1. 指数位的设置能精确表达整数幂次
  2. 尾数位的线性插值引入可控误差
  3. 参数c的最优选择使均方误差最小化

这种基于IEEE754内存位操作的EXP算法,在保证计算效率的同时,提供了理论严谨的误差控制机制。

从数学推导到代码实现的映射解析

一、核心公式到代码的转换

1. 核心公式回顾

算法核心公式:
i = 2 20 ln ⁡ 2 ⏟ E X P _ A ⋅ y + ( 1072693248 − 60801 ) ⏟ 基值修正 i = \underbrace{\frac{2^{20}}{\ln2}}_{EXP\_A} \cdot y + \underbrace{(1072693248 - 60801)}_{基值修正} i=EXP_A ln2220y+基值修正 (107269324860801)

2. 代码要素分解
数学要素代码实现数值说明
2 20 / ln ⁡ 2 2^{20}/\ln2 220/ln2#define EXP_A (1048576/M_LN2)1048576=2²⁰
基值修正项1072693248 - EXP_C1072693248=0x3FF00000
整体位操作eco.n.i = ...直接操作双精度浮点高位内存

二、关键内存布局操作

1. 双精度浮点内存结构
// 内存布局(小端序):
struct {
    uint32_t j;  // 低32位:尾数低位(实际未使用)
    uint32_t i;  // 高32位:符号位+指数位+尾数高位
} n;

```c
// C语言实现示例
union {
    double d;
    struct {
        uint32_t i;  // 高位字(含符号位和指数位)
        uint32_t j;  // 低位字(尾数位)
    } n;
} eco;
#define EXP_A (1048576 / M_LN2)  // 2^20 / ln2 ≈ 1512775.916
#define EXP_C 60801              // 最优误差修正参数

#define EXP(y) (eco.n.i = EXP_A*(y) + (1072693248 - EXP_C), eco.d)

或者有一种更好解读的方式,就是:

inline double fast_exp(double y) {
    double d;
    *(reinterpret_cast<int*>(&d) + 0) = 0;
    *(reinterpret_cast<int*>(&d) + 1) = static_cast<int>(1512775 * y + 1072632447); 
    // 2^20 / ln2 ≈ 1512775.916 其中最优误差修正参数为60801 
    //1072693248 实际是 0x3FF00000 的十进制形式,其作用包括:
    //1. 初始化指数偏置:将基准指数设为 1023(对应实际指数 0)
    //2. 尾数清零:隐式设置尾数为 1.0(规格化数的隐含前导1)
    //3. 快速位操作:通过整数加法直接修改指数位
    return d;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值