近0端多项式逼近# sin(x)
的泰勒展开:
sin ( x ) = x − 1 3 ! x 3 + 1 5 ! x 5 − 1 7 ! x 7 + 1 9 ! x 9 + o ( x 9 ) \sin(x) = x - \frac{1}{3!}x^3+\frac{1}{5!}x^5-\frac{1}{7!}x^7+\frac{1}{9!}x^9+o(x^9) sin ( x ) = x − 3 ! 1 x 3 + 5 ! 1 x 5 − 7 ! 1 x 7 + 9 ! 1 x 9 + o ( x 9 ) cos(x)
的泰勒展开:
cos ( x ) = 1 − 1 2 ! x 2 + 1 4 ! x 4 − 1 6 ! x 6 + 1 8 ! x 8 + o ( x 8 ) \cos(x) = 1 - \frac{1}{2!}x^2+\frac{1}{4!}x^4-\frac{1}{6!}x^6+\frac{1}{8!}x^8+o(x^8) cos ( x ) = 1 − 2 ! 1 x 2 + 4 ! 1 x 4 − 6 ! 1 x 6 + 8 ! 1 x 8 + o ( x 8 ) 当然,因为展开项数限制和数的精度限制,x的范围只在靠近0的时候才有比较高的精度。
float sinf_poly ( float x ) {
float r, t, s = x * x;
r = 2.86567956 e- 6 f ; // 0x1.80a000p-19
r = r * s - 1.98559923 e- 4 f ; // -0x1.a0690cp-13
r = r * s + 8.33338592 e- 3 f ; // 0x1.111182p-07
r = r * s - 1.66666672 e- 1 f ; // -0x1.555556p-03
t = x * s + 0.0 f ; // ensure -0 is passed through
r = r * t + x;
return r;
}
float cosf_poly ( float x ) {
float r, s = x * x;
r = 2.44677067 e- 5 f ;
r = r * s - 1.38877297 e- 3 f ;
r = r * s + 4.16666567 e- 2 f ;
r = r * s - 5.00000000 e- 1 f ;
r = r * s + 1.00000000 e+ 0 f ;
return r;
}
因此,需要进行Reduction,这是这一篇的重点。
Reduction# Cody-Waite Reduction# 因为上面的多项式要在靠近0的时候才会有比较高的精度,因此,就需要把x
归约到0附近。理论上来说,我们实际上只需要把x归约到[ − π 2 , π 2 ] [-\frac{\pi}{2}, \frac{\pi}{2}] [ − 2 π , 2 π ] 附近即可。数学上的方法为:
x ′ = x − ⌊ x π 2 ⌋ × π 2 x' =x - \lfloor \frac{x}{\frac{\pi}{2}} \rfloor \times \frac{\pi}{2} x ′ = x − ⌊ 2 π x ⌋ × 2 π 例如,当x = 16
时,有如下的计算过程:
x = 16.0 x π / 2 ≈ 10.1859 ⌊ x π / 2 ⌋ = 10 ⌊ x π / 2 ⌋ × π 2 = 15.7079 x − ⌊ x π / 2 ⌋ × π 2 = 0.2920 \begin{aligned} &x = 16.0 \\ &\frac{x}{\pi/2} \approx 10.1859 \\ &\lfloor \frac{x}{\pi/2} \rfloor = 10 \\ &\lfloor \frac{x}{\pi/2} \rfloor \times \frac{\pi}{2} = 15.7079 \\ &x - \lfloor \frac{x}{\pi/2} \rfloor \times \frac{\pi}{2} = 0.2920 \end{aligned} x = 16.0 π /2 x ≈ 10.1859 ⌊ π /2 x ⌋ = 10 ⌊ π /2 x ⌋ × 2 π = 15.7079 x − ⌊ π /2 x ⌋ × 2 π = 0.2920 注意这里⌊ x π / 2 ⌋ = 10 \lfloor \frac{x}{\pi/2} \rfloor = 10 ⌊ π /2 x ⌋ = 10 ,这也就意味着我们得到的x ′ x' x ′ 与x x x 有:
sin ( x ) = sin ( x ′ + 10 × π 2 ) = sin ( x ′ + π ) = − sin ( x ′ ) \sin(x) = \sin(x'+10\times \frac{\pi}{2}) = \sin(x'+\pi) = -\sin(x') sin ( x ) = sin ( x ′ + 10 × 2 π ) = sin ( x ′ + π ) = − sin ( x ′ ) 因此,将x'
代入到sin
的多项式中,再取负号即可。
再举一个例子,当x=18
时,我们有⌊ x π / 2 ⌋ = 11 \lfloor \frac{x}{\pi/2} \rfloor = 11 ⌊ π /2 x ⌋ = 11 ,此时x ′ x' x ′ 与x x x 有:
sin ( x ) = sin ( x ′ + 11 × π 2 ) = sin ( x ′ + 3 π 2 ) = − sin ( x ′ + π 2 ) = − cos ( x ′ ) \sin(x) = \sin(x'+11\times \frac{\pi}{2}) = \sin(x'+\frac{3\pi}{2}) = -\sin(x'+\frac{\pi}{2}) = -\cos(x') sin ( x ) = sin ( x ′ + 11 × 2 π ) = sin ( x ′ + 2 3 π ) = − sin ( x ′ + 2 π ) = − cos ( x ′ ) 因此此时,是把x'
代入到cos
的多项式中,再取负号。
按照上面的理论,可以有代码:
float j; int q;
j = a * 6.36619747 e- 1 f + 1.2582912 e+ 7 f ; // 6.36619747e-1即为2/pi的近似值
j = j - 1.25829120 e+ 7 f ;
r = a - j * 1.57078552 e+ 00 f ;
r = r - j * 1.08043314 e- 05 f ;
r = r - j * 2.56334407 e- 12 f ;
q = ( int )j;
不过可惜的是,如果给的x
过大的话,上面的代码会失效。其中的一个重点问题是通过单精度浮点数0x3f22f983
表示的2 / π 2/\pi 2/ π 精度是不够的,假设这里的x
的值是100000000,那么算出来的j
的结果是63661976
,但是实际上,如果换用double的话,可以算出j
的实际值为63661977
,这样一来就造成了偏差。因此,对于大数而言,我们必须另外想办法。
Payne-Hanek Reduction# 对于比较大的数,Cody-Waite的算法就会失效,主要的问题在于Cody-Waite算法只使用到固定精度的浮点数,对于超大的数精度就会不够。1983年Payne与Hanek提出把浮点运算转换为大整数运算,这样来解决比较大型的浮点数的归约问题。但相对于Cody-Waite算法来说,Payne-Hanek算法相当不好理解。需要一些数学上的推理才可以表述清楚。
Mathematical representation of Cody-Waite# 对于给定的数x x x 我们希望把x x x 给归约到C C C 中,那么我们使用的公式为:
x − k C , where k = ⌊ x C ⌋ x - kC, \text{ where } k = \lfloor \frac{x}{C}\rfloor x − k C , where k = ⌊ C x ⌋ Mathematical representation of Payne-Hanek# Payne与Hanek的论文中主要是针对三角函数的归约问题,这一点很重要,如果不是三角函数的话是不能用这个算法的,也就是说,现在给定x x x 我们希望计算:
y = x − k C , where k = ⌊ x C ⌋ , C = π 2 (1) y = x - kC, \text{ where } k = \lfloor \frac{x}{C}\rfloor, C = \frac{\pi}{2} \tag{1} y = x − k C , where k = ⌊ C x ⌋ , C = 2 π ( 1 ) 对于IEEE754下的浮点数x x x ,我们规定x x x 的阶码e x e_x e x ,尾数位p p p ,那么x x x 的尾数X X X 与x x x 有如下的数学关系:
x = X × 2 e x − p x = X \times 2 ^ {e_x - p } x = X × 2 e x − p or:
X = x × 2 p − e x (2) X = x\times 2^{p-e_x} \tag{2} X = x × 2 p − e x ( 2 ) 例如对于一个float
数384,1. 1 2 × 2 8 1.1_2\times 2^8 1. 1 2 × 2 8 ,十进制表示为384.0,十六进制表示为0x43c00000
,它的尾数是0x400000
,但是根据IEE754标准,我们知道要填一个1,所以它的尾数就要变成0xc00000
,也就是12582912。如果直接运算的话就是:
X = 384 × 2 23 − 8 = 12582912 X = 384 \times 2^{23 - 8} = 12582912 X = 384 × 2 23 − 8 = 12582912 现在我们把(2)给代入到(1)当中,可以得到:
y = X × 2 e x − p − k C (3) y = X\times 2^{e_x-p} - kC \tag{3} y = X × 2 e x − p − k C ( 3 ) 然后把C = π / 2 C=\pi/2 C = π /2 代入,把(3)式子变形成:
y = π 2 ( 2 π 2 e x − p X − k ) (4) y = \frac{\pi}{2}(\frac{2}{\pi} 2^{e_x - p}X - k) \tag{4} y = 2 π ( π 2 2 e x − p X − k ) ( 4 ) 接下来的问题是我们怎么去处理这个2 / π × 2 e x − p 2/\pi \times 2^{e_x - p} 2/ π × 2 e x − p ,我们把2 / π 2/\pi 2/ π 的200位二进制写在下面:
0.10100010111110001100000110110111001001110010001000001010
100101001111111000010011101010111110100011111010100110100
110111011100000011011011011000101001010110011001001111000
100001110010000010000011111111001010001011...
我们把这个2 / π 2/\pi 2/ π ,看成如下的形式:
0. v 1 v 2 v 3 v 4 v 5 . . . 0.v_1v_2v_3v_4v_5... 0. v 1 v 2 v 3 v 4 v 5 ... 这样一来我们就有如下的数学表达式:
2 π = v 1 2 − 1 + v 2 2 − 2 + . . . = ∑ i = 1 ∞ v i 2 − i , where v i = 0 or 1 (5) \frac{2}{\pi} = v_12^{-1} + v_22^{-2}+... = \sum_{i=1}^\infty v_i2^{-i}, \text{ where } v_i = 0 \text{ or } 1 \tag{5} π 2 = v 1 2 − 1 + v 2 2 − 2 + ... = i = 1 ∑ ∞ v i 2 − i , where v i = 0 or 1 ( 5 ) 然后我们把(5)式再代入带(4)当中:
y = ∑ i = 1 ∞ v i π 2 2 e x − p − i X − π 2 k = ∑ i = 1 ∞ v i 2 e x − p − i − 1 X − π 2 k y = \sum_{i=1}^\infty v_i\frac{\pi}{2} 2^{e_x-p-i}X - \frac{\pi}{2}k =\sum_{i=1}^\infty v_i 2^{e_x-p-i-1}X - \frac{\pi}{2}k y = i = 1 ∑ ∞ v i 2 π 2 e x − p − i X − 2 π k = i = 1 ∑ ∞ v i 2 e x − p − i − 1 X − 2 π k 我们考察其中连加式的第i
项,是v i 2 e x − p − i − 1 v_i 2^{e_x-p-i-1} v i 2 e x − p − i − 1 ,这里的v i v_i v i 要么是0要么是1,当然我们不去理会它是0的情况,只考虑它是1,那么这一项就变成了2 e x − p − i − 1 π 2^{e_x-p-i-1}\pi 2 e x − p − i − 1 π 。注意回顾一下我们本来的目的,我们的目的是要把一个大数x x x 进行归约。但是要针对三角函数。三角函数都是以2 π 2\pi 2 π 为周期的周期函数。那么如果2 e x − p − i − 1 π 2^{e_x-p-i-1}\pi 2 e x − p − i − 1 π 是2 π 2\pi 2 π 的倍数,自然也就不需要计算了,换句话说,对于i ≤ e x − p − 1 i\leq e_x-p-1 i ≤ e x − p − 1 的项而言,我们是可以不必计算的,只需要计算i ≥ e x − p − 1 i\ge e_x-p-1 i ≥ e x − p − 1 之后的项即可。
let m = e x − p − 1 change the formula to ∑ i = 1 ∞ v i 2 m − i X − π 2 k \text{let } m = e_x -p -1 \text{ change the formula to } \sum_{i=1}^\infty v_i 2^{m-i}X - \frac{\pi}{2}k let m = e x − p − 1 change the formula to i = 1 ∑ ∞ v i 2 m − i X − 2 π k 我们先假定m > 0 m > 0 m > 0 ,大到这个连加式的前几项m − i m-i m − i 都是整数,那么把这个式子变成:
y = ∑ i = 1 m − 1 v i 2 m − i X + ∑ i = m ∞ v i 2 m − i X − π 2 k y = \sum_{i=1}^{m-1} v_i 2^{m-i}X +\sum_{i=m}^{\infty} v_i 2^{m-i}X - \frac{\pi}{2}k y = i = 1 ∑ m − 1 v i 2 m − i X + i = m ∑ ∞ v i 2 m − i X − 2 π k 这个式子中的第一个连加项必定是2 π 2\pi 2 π 的倍数,因此我们直接用一个2 N π 2N\pi 2 N π 来代替就好,仅仅考虑后面的项:
y = ∑ i = 1 m − 1 v i 2 m − i X + ∑ i = m ∞ v i 2 m − i X − π 2 k = 2 N π + ∑ i = m ∞ v i 2 m − i X − π 2 k y = \sum_{i=1}^{m-1} v_i 2^{m-i}X +\sum_{i=m}^{\infty} v_i 2^{m-i}X - \frac{\pi}{2}k = 2N\pi+\sum_{i=m}^{\infty} v_i 2^{m-i}X-\frac{\pi}{2}k y = i = 1 ∑ m − 1 v i 2 m − i X + i = m ∑ ∞ v i 2 m − i X − 2 π k = 2 N π + i = m ∑ ∞ v i 2 m − i X − 2 π k 对于中间的项,实际当中我们不可能加到无穷项,假设我们只加到m + l m+l m + l 项,那么我们做如下的处理:
∑ i = m m + l v i 2 m − i X = 2 − ( m + l ) 2 m + l ∑ i = m m + l v i 2 m − i X = 2 − ( m + l ) ∑ i = m m + l v i 2 2 m + l − i X \sum_{i=m}^{m+l} v_i 2^{m-i}X = 2^{-(m+l)}2^{m+l} \sum_{i=m}^{m+l} v_i 2^{m-i}X = 2^{-(m+l)}\sum_{i=m}^{m+l} v_i 2^{2m+l-i}X i = m ∑ m + l v i 2 m − i X = 2 − ( m + l ) 2 m + l i = m ∑ m + l v i 2 m − i X = 2 − ( m + l ) i = m ∑ m + l v i 2 2 m + l − i X 现在我们来关注一个问题,这个公式的含义:
∑ i = m m + l v i 2 2 m + l − i \sum_{i=m}^{m+l} v_i 2^{2m+l-i} i = m ∑ m + l v i 2 2 m + l − i 实际上代表了π / 2 \pi/2 π /2 从第m位开始,到第m + l m+l m + l 位为止的二进制整数。这个整数乘以X X X ,再乘以2 − ( m + l ) 2^{-(m+l)} 2 − ( m + l ) ,可以得到一个数,让这个k k k 取这个数的整数部分,那么这个数的小数部分乘以π / 2 \pi/2 π /2 就是我们要求的y y y 值了。
Example:# 求x = 1. 1 2 × 2 30 x=1.1_2\times 2^{30} x = 1. 1 2 × 2 30 经过Paney-Hanek算法归约后的数。经过上述的论证,我们可以得到以下的一些变量:
x = 1. 1 2 × 2 30 = 1.5 × 2 30 ⇒ { e x = 30 p = 23 X = 12582912 m = e x − p − 1 = 6 x = 1.1_2\times 2^{30} = 1.5\times 2^{30} \Rightarrow \begin{cases} e_x = 30 \\ p = 23 \\ X = 12582912 \\ m = e_x-p-1=6 \end{cases} x = 1. 1 2 × 2 30 = 1.5 × 2 30 ⇒ ⎩ ⎨ ⎧ e x = 30 p = 23 X = 12582912 m = e x − p − 1 = 6 因此我们从π / 2 \pi/2 π /2 的第6个小数位开始取,连续取64位,取的位数必须超过p
的值,否则最后算的时候会发现没有小数部分。取得位数越多越精确。这里是1011111000110000011011011100100111001000100000101010010100111111
,它的十进制值是13704574379508278591
,然后我们可以得到一个G G G
G = 13704574379508278591 2 64 G = \frac{13704574379508278591}{2^{64}} G = 2 64 13704574379508278591 然后计算G × X G\times X G × X ,得到37392713.36464738
,我们只要令k=37392713
,那么小数部分0.36464738
乘以π / 2 \pi/2 π /2 ,就可以得到0.5727867650793,这也就是我们要求的y
值。
不过上面的论述有一点不足,就是上面我们是假定m > 0 m>0 m > 0 的,有的时候x x x 也可能没有达到一定程度,因而m m m 也可能小于0,我们重新来论述一下:
y = ∑ i = 1 ∞ v i 2 m − i X − π 2 k y = \sum_{i=1}^\infty v_i 2^{m-i}X - \frac{\pi}{2}k y = i = 1 ∑ ∞ v i 2 m − i X − 2 π k 这里面的m m m 小于0,将其用− n -n − n 来替代:
y = ∑ i = 1 ∞ v i 2 m − i X − π 2 k = ∑ i = 1 ∞ v i 2 − n − i X − π 2 k = 2 − n ∑ i = 1 ∞ v i 2 − i X − π 2 k \begin{aligned} y &= \sum_{i=1}^\infty v_i 2^{m-i}X - \frac{\pi}{2}k \\ &=\sum_{i=1}^\infty v_i 2^{-n-i}X - \frac{\pi}{2}k \\ &=2^{-n}\sum_{i=1}^\infty v_i 2^{-i}X - \frac{\pi}{2}k \end{aligned} y = i = 1 ∑ ∞ v i 2 m − i X − 2 π k = i = 1 ∑ ∞ v i 2 − n − i X − 2 π k = 2 − n i = 1 ∑ ∞ v i 2 − i X − 2 π k 上面的公式表明我们取数的时候要从2 / π 2/\pi 2/ π 的第一个小数点就要开始取了,假定我们往后取l l l 项:
y = 2 − n ∑ i = 1 l v i 2 − i X − π 2 k = 2 − n 2 l 2 − l ∑ i = 1 l v i 2 − i X − π 2 k = 2 − ( n + l ) ∑ i = 1 l v i 2 l − i X − π 2 k \begin{aligned} y &= 2^{-n}\sum_{i=1}^l v_i 2^{-i}X - \frac{\pi}{2}k \\ &= 2^{-n}2^l2^{-l}\sum_{i=1}^l v_i 2^{-i}X - \frac{\pi}{2}k \\ & = 2^{-(n+l)}\sum_{i=1}^l v_i 2^{l-i}X - \frac{\pi}{2}k \end{aligned} y = 2 − n i = 1 ∑ l v i 2 − i X − 2 π k = 2 − n 2 l 2 − l i = 1 ∑ l v i 2 − i X − 2 π k = 2 − ( n + l ) i = 1 ∑ l v i 2 l − i X − 2 π k 这里面的∑ i = 1 l v i 2 l − i \sum_{i=1}^l v_i 2^{l-i} ∑ i = 1 l v i 2 l − i 就代表着2 / π 2/\pi 2/ π 从第1个小数位到第l l l 个小数位的整数,然后这个整数要除以2 − ( n + l ) 2^{-(n+l)} 2 − ( n + l ) ,乘以X X X 后得到的数跟先前类似,得到的小数部分与π / 2 \pi/2 π /2 相乘即可得到最终的归约值。
Example# 来看先前x = 384 x=384 x = 384 的例子:
x = 384 ⇒ { e x = 8 p = 23 X = 12582912 n = − ( 8 − 23 − 1 ) = 16 x = 384 \Rightarrow \begin{cases} e_x = 8 \\ p = 23 \\ X = 12582912 \\ n = -(8-23-1) = 16\\ \end{cases} x = 384 ⇒ ⎩ ⎨ ⎧ e x = 8 p = 23 X = 12582912 n = − ( 8 − 23 − 1 ) = 16 让l = 48 l=48 l = 48 ,这样n + l = 64 n+l=64 n + l = 64 ,取出的数为10100010111110001100000110110111001001110010001000001010
,十进制表示为179189285594914
,然后运算:
G × X = 179189285594914 × 12582912 2 63 G \times X = \frac{179189285594914 \times 12582912 }{2^{63}} G × X = 2 63 179189285594914 × 12582912 注意这里是63。
然后可以得到小数部分为:0.457558794
,乘以π / 2 \pi/2 π /2 得到0.718731673
,非常接近我们要计算的值。