BigArithmetic - 提供任意精度的算术运算的静态类

在上一篇随笔“使用快速傅里叶变换计算大整数乘法”中,已经讲述了使用快速傅里叶变换计算大整数乘法的原理。在这一篇随笔中,我们就使用快速傅里叶变换来实现一个提供任意精度的算术运算的静态类:BigArithmetic。

下面就是 BigArithmetic 类源程序代码:

  1  using  System;
  2  using  System.Diagnostics;
  3 
  4  namespace  Skyiv.Numeric
  5  {
  6     ///   <summary>
  7     ///  提供任意精度的算术运算的静态类。使用快速傅里叶变换。
  8     ///  本类对字节数组进行算术运算,字节数组以 100 为基。
  9     ///  字节数组中第一个元素存储的数字是最高有效位。
 10     ///   </summary>
 11     static   class  BigArithmetic
 12    {
 13       // = C语言数值算法程序大全(第二版),ISBN 7-5053-2931-6 / TP 993
 14       // = Numerical Recipes in C, The Art of Scientific Computing, Second Edition
 15       // = Cambridge University Press 1988, 1992
 16       // = [美] W.H.Press, S.A.Teukolsky, W.T.Vetterling, B.P.Flannery 著
 17       // = 傅祖芸 赵梅娜 丁岩 等译,傅祖芸 校,电子工业出版社,1995年10月第一版
 18 
 19       static   readonly   byte  Len  =   2 //  字节数组的元素包含的十进制数字的个数
 20       static   readonly   byte  Base  =  ( byte )Math.Pow( 10 , Len);  //  字节数组的基
 21       static   readonly   byte  MaxValue  =  ( byte )(Base  -   1 );     //  字节数组的元素的最大值
 22 
 23       // = pp.431-432, four1, 12.2 快速傅里叶变换(FFT)
 24       ///   <summary>
 25       ///  复函数的快速傅里叶变换
 26       ///  变量 nn 是复数据点的个数,实型数组 data[1..2*nn] 的实际界长是两倍 nn,
 27       ///  而每个复数值占据了两个相继的存储单元。nn 必须是 2 的整数幂
 28       ///   </summary>
 29       ///   <param name="data"> 实型数组 data[1..2*nn]。注意,下标从 1 开始 </param>
 30       ///   <param name="isInverse"> 是否逆变换。注意: 逆变换未乘上归一化因子 1/nn </param>
 31       public   static   void  ComplexFFT( double [] data,  bool  isInverse)
 32      {
 33         int  n  =  data.Length  -   1 //  n 必须是 2 的正整数幂
 34         int  nn  =  n  >>   1 ;          //  变量 nn 是复数据点的个数
 35         for  ( int  i  =   1 , j  =   1 ; i  <  n; i  +=   2 //  这个循环实现位序颠倒
 36        {
 37           if  (j  >  i)
 38          {
 39            Utility.Swap( ref  data[j],  ref  data[i]);
 40            Utility.Swap( ref  data[j  +   1 ],  ref  data[i  +   1 ]);
 41          }
 42           int  m  =  nn;
 43           for  (; m  >=   2   &&  j  >  m; m  >>=   1 ) j  -=  m;
 44          j  +=  m;
 45        }
 46         for  ( int  mmax  =   2 , istep  =   4 ; n  >  mmax; mmax  =  istep)  //  执行 log2(nn) 次外循环
 47        {
 48          istep  =  mmax  <<   1 //  下面是关于三角递归的初始赋值
 49           double  theta  =  (isInverse  ?   - 2  :  2 *  Math.PI  /  mmax;
 50           double  wtemp  =  Math.Sin( 0.5   *  theta);
 51           double  wpr  =   - 2   *  wtemp  *  wtemp;
 52           double  wpi  =  Math.Sin(theta);
 53           double  wr  =   1 ;
 54           double  wi  =   0 ;
 55           for  ( int  m  =   1 ; m  <  mmax; m  +=   2 )
 56          {
 57             for  ( int  i  =  m; i  <=  n; i  +=  istep)
 58            {
 59               int  j  =  i  +  mmax;  //  下面是 Danielson-Lanczos 公式
 60               double  tempr  =  wr  *  data[j]  -  wi  *  data[j  +   1 ];
 61               double  tempi  =  wr  *  data[j  +   1 +  wi  *  data[j];
 62              data[j]  =  data[i]  -  tempr;
 63              data[j  +   1 =  data[i  +   1 -  tempi;
 64              data[i]  +=  tempr;
 65              data[i  +   1 +=  tempi;
 66            }
 67            wr  =  (wtemp  =  wr)  *  wpr  -  wi  *  wpi  +  wr;  //  三角递归
 68            wi  =  wi  *  wpr  +  wtemp  *  wpi  +  wi;
 69          }
 70        }
 71      }
 72 

该类的第一个方法是 ComplexFFT,计算复函数的快速傅里叶变换。注意,ComplexFFT 并没有使用复数(不象 C++,C# 也没有提供复数),而是让每个复数值占据实型数组的两个相继的存储单元。还有,要求输入的复数据点的个数必须是 2 的整数幂。该方法也能够计算复函数的快速傅里叶逆变换。

该程序的算法是使用 1942 年 Danielson 和 Lanczos 证明的引理:一个界长为 N 的离散傅里叶变换可以重新写成两个界长各为 N/2 的离散傅里叶变换之和。在算法的第一部分,将数据整理成位序颠倒的次序。而在第二部分,有一个执行 log2N 次的外循环。它依次计算界长为 2, 4, 8, ..., N 的变换。对于这一过程的每一步来说,为了履行 Danielson-Lanczos 引理,有两个嵌套的内循环,其涉及到已计算的子变换和每个变换的元素。通过限制外部调用正弦和余弦到外层循环,可以使运算更有效,在外层循环中只要调用它们 log2N 次。倍角的正弦和余弦的计算是通过内循环中简单的递归关系进行的,如下所示:

cos(θ + δ) = cosθ - [ α cosθ  +  βsinθ ]
sin(θ + δ) = sinθ - [ α sinθ  -  βcosθ ]

其中 α, β 是预先计算的系数:α = 2 sin2(δ/2),  β = sinδ 。

 

 73       // = pp.436, realft, 12.3.2 单个实函数的 FFT
 74       ///   <summary>
 75       ///  单个实函数的快速傅里叶变换
 76       ///  计算一组 n 个实值数据点的傅里叶变换。用复傅里叶变换的正半频率替换这些数据,
 77       ///  它存储在数组 data[1..n] 中。复变换的第一个和最后一个分量的实数值分别返回
 78       ///  单元 data[1] 和 data[2] 中。n 必须是 2 的幂次。这个程序也能计算复数据数组
 79       ///  的逆变换,只要该数组是实值数据的变换(在这种情况下,其结果必须乘以 1/n)即可。
 80       ///   </summary>
 81       ///   <param name="data"> 实型数组 data[1..n]。注意,下标从 1 开始 </param>
 82       ///   <param name="isInverse"> 是否逆变换。注意: 逆变换未乘上归一化因子 1/n </param>
 83       public   static   void  RealFFT( double [] data,  bool  isInverse)
 84      {
 85         int  n  =  data.Length  -   1 //  n 必须是 2 的整数幂
 86         if  ( ! isInverse) ComplexFFT(data, isInverse);  //  此处是正向变换
 87         double  theta  =  (isInverse  ?   - 2  :  2 *  Math.PI  /  n;  //  递归的初始赋值
 88         double  wtemp  =  Math.Sin( 0.5   *  theta);
 89         double  wpr  =   - 2   *  wtemp  *  wtemp;
 90         double  wpi  =  Math.Sin(theta);
 91         double  wr  =   1   +  wpr;
 92         double  wi  =  wpi;
 93         double  c1  =   0.5 ;
 94         double  c2  =  isInverse  ?   0.5  :  - 0.5 ;
 95         int  n3  =  n  +   3 ;
 96         int  n4  =  n  >>   2 ;
 97         for  ( int  i  =   2 ; i  <=  n4; i ++ )
 98        {
 99           int  i1  =  i  +  i  -   1 , i2  =  i1  +   1 , i3  =  n3  -  i2, i4  =  i3  +   1 ;
100           double  h1r  =  c1  *  (data[i1]  +  data[i3]);  //  两个分离变换是
101           double  h1i  =  c1  *  (data[i2]  -  data[i4]);  //  从 data 中分离出来
102           double  h2r  =   - c2  *  (data[i2]  +  data[i4]);
103           double  h2i  =  c2  *  (data[i1]  -  data[i3]);
104          data[i1]  =  h1r  +  wr  *  h2r  -  wi  *  h2i;  //  此处重新组合以形成
105          data[i2]  =  h1i  +  wr  *  h2i  +  wi  *  h2r;  //  原始实型数据的真实变换
106          data[i3]  =  h1r  -  wr  *  h2r  +  wi  *  h2i;
107          data[i4]  =   - h1i  +  wr  *  h2i  +  wi  *  h2r;
108          wr  =  (wtemp  =  wr)  *  wpr  -  wi  *  wpi  +  wr;  //  递归式
109          wi  =  wi  *  wpr  +  wtemp  *  wpi  +  wi;
110        }
111         double  tmp  =  data[ 1 ];
112         if  ( ! isInverse)
113        {
114          data[ 1 =  tmp  +  data[ 2 ];  //  同时挤压第一个和最后一个数据
115          data[ 2 =  tmp  -  data[ 2 ];  //  使它们都在原始数组中
116        }
117         else
118        {
119          data[ 1 =  c1  *  (tmp  +  data[ 2 ]);
120          data[ 2 =  c1  *  (tmp  -  data[ 2 ]);
121          ComplexFFT(data, isInverse);  //  此处是逆变换
122        }
123      }
124 

第二个方法是 RealFFT,计算单个实函数的快速傅里叶变换。因为在很多情况下期望求快速傅里叶变换的数据由实值样本组成。如果将这些样本放入一个复型数组中,并令其所有虚部为零的话,从执行时间和对存储需求二者来看,其效率是很低的。所以,我们将原始数据放入一个界长只有一半的复型数组中,其偶数项放入该数组的实部,奇数项放入它的虚部。然后调用 ComplexFFT 来计算快速傅里叶变换。


125       ///   <summary>
126       ///  比较 x[0..n-1] 和 y[0..n-1]
127       ///   </summary>
128       ///   <param name="x"> 第一操作数 x[0..n-1] </param>
129       ///   <param name="y"> 第二操作数 y[0..n-1] </param>
130       ///   <param name="n"> 两个操作数 x 和 y 的精度 </param>
131       ///   <returns> 比较结果:-1:小于 1:大于 0:等于 </returns>
132       public   static   int  Compare( byte [] x,  byte [] y,  int  n)
133      {
134        Debug.Assert(x.Length  >=  n  &&  y.Length  >=  n);
135         for  ( int  i  =   0 ; i  <  n; i ++ )
136           if  (x[i]  !=  y[i])
137             return  (x[i]  <  y[i])  ?   - 1  :  1 ;
138         return   0 ;
139      }
140 
141       // = pp.775, mpneg, 20.6 任意精度的运算
142       ///   <summary>
143       ///  求补码。注意,被操作数被修改。
144       ///   </summary>
145       ///   <param name="data"> 被操作数 data[0..n-1] </param>
146       ///   <param name="n"> 被操作数 data 的精度 </param>
147       ///   <returns> 被操作数的补码 data[0..n-1] </returns>
148       public   static   byte [] Negative( byte [] data,  int  n)
149      {
150        Debug.Assert(data.Length  >=  n);
151         for  ( int  k  =  Base, i  =  n  -   1 ; i  >=   0 ; i -- )
152          data[i]  =  ( byte )((k  =  MaxValue  +  k  /  Base  -  data[i])  %  Base);
153         return  data;
154      }
155 
156       // = pp.774, mpsub, 20.6 任意精度的运算
157       ///   <summary>
158       ///  减法。从 minuend[0..n-1] 中减去 subtrahend[0..n-1],得到 difference[0..n-1]
159       ///   </summary>
160       ///   <param name="difference"> 差 difference[0..n-1] </param>
161       ///   <param name="minuend"> 被减数 minuend[0..n-1] </param>
162       ///   <param name="subtrahend"> 减数 subtrahend[0..n-1] </param>
163       ///   <param name="n"> 被减数 minuend 和减数 subtrahend 的精度 </param>
164       ///   <returns> 差 difference[0..n-1] </returns>
165       public   static   byte [] Subtract( byte [] difference,  byte [] minuend,  byte [] subtrahend,  int  n)
166      {
167        Debug.Assert(minuend.Length  >=  n  &&  subtrahend.Length  >=  n  &&  difference.Length  >=  n);
168         for  ( int  k  =  Base, i  =  n  -   1 ; i  >=   0 ; i -- )
169          difference[i]  =  ( byte )((k  =  MaxValue  +  k  /  Base  +  minuend[i]  -  subtrahend[i])  %  Base);
170         return  difference;
171      }
172 
173       // = pp.774, mpadd, 20.6 任意精度的运算
174       ///   <summary>
175       ///  加法。augend[0..n-1] 与 addend[0..n-1] 相加,得到 sum[0..n]
176       ///   </summary>
177       ///   <param name="sum"> 和 sum[0..n] </param>
178       ///   <param name="augend"> 被加数 augend[0..n-1] </param>
179       ///   <param name="addend"> 加数 addend[0..n-1] </param>
180       ///   <param name="n"> 被加数 augend 和加数 addend 的精度 </param>
181       ///   <returns> 和 sum[0..n] </returns>
182       public   static   byte [] Add( byte [] sum,  byte [] augend,  byte [] addend,  int  n)
183      {
184        Debug.Assert(augend.Length  >=  n  &&  addend.Length  >=  n  &&  sum.Length  >=  n  +   1 );
185         int  k  =   0 ;
186         for  ( int  i  =  n  -   1 ; i  >=   0 ; i -- )
187          sum[i  +   1 =  ( byte )((k  =  k  /  Base  +  augend[i]  +  addend[i])  %  Base);
188        sum[ 0 +=  ( byte )(k  /  Base);
189         return  sum;
190      }
191 
192       // = pp.774, mpadd, 20.6 任意精度的运算
193       ///   <summary>
194       ///  捷加法。augend[0..n-1] 与整数 addend 相加,得到 sum[0..n]
195       ///   </summary>
196       ///   <param name="sum"> 和 sum[0..n] </param>
197       ///   <param name="augend"> 被加数 augend[0..n-1] </param>
198       ///   <param name="n"> 被加数 augend 的精度 </param>
199       ///   <param name="addend"> 加数 addend </param>
200       ///   <returns> 和 sum[0..n] </returns>
201       public   static   byte [] Add( byte [] sum,  byte [] augend,  int  n,  byte  addend)
202      {
203        Debug.Assert(augend.Length  >=  n  &&  sum.Length  >=  n  +   1 );
204         int  k  =  Base  *  addend;
205         for  ( int  i  =  n  -   1 ; i  >=   0 ; i -- )
206          sum[i  +   1 =  ( byte )((k  =  k  /  Base  +  augend[i])  %  Base);
207        sum[ 0 +=  ( byte )(k  /  Base);
208         return  sum;
209      }
210 
211       // = pp.775, mpsdv, 20.6 任意精度的运算
212       ///   <summary>
213       ///  捷除法。dividend[0..n-1] 除以整数 divisor,得到 quotient[0..n-1]
214       ///   </summary>
215       ///   <param name="quotient"> 商 quotient[0..n-1] </param>
216       ///   <param name="dividend"> 被除数 dividend[0..n-1] </param>
217       ///   <param name="n"> 被除数 dividend 的精度 </param>
218       ///   <param name="divisor"> 除数 divisor </param>
219       ///   <returns> 商 quotient[0..n-1] </returns>
220       public   static   byte [] Divide( byte [] quotient,  byte [] dividend,  int  n,  byte  divisor)
221      {
222        Debug.Assert(quotient.Length  >=  n  &&  dividend.Length  >=  n);
223         for  ( int  r  =   0 , k  =   0 , i  =   0 ; i  <  n; i ++ , r  =  k  %  divisor)
224          quotient[i]  =  ( byte )((k  =  Base  *  r  +  dividend[i])  /  divisor);
225         return  quotient;
226      }
227 

接下来是比较、求补码、减法、加法、捷加法、捷除法,都相当简单,程序中已经有详细的注释了。


228       // = pp.776-777, mpmul, 20.6 任意精度的运算
229       ///   <summary>
230       ///  乘法。multiplicand[0..n-1] 与 multiplier[0..m-1] 相乘,得到 product[0..n+m-1]
231       ///   </summary>
232       ///   <param name="product"> 积 product[0..n+m-1] </param>
233       ///   <param name="multiplicand"> 被乘数 multiplicand[0..n-1] </param>
234       ///   <param name="n"> 被乘数 multiplicand 的精度 </param>
235       ///   <param name="multiplier"> 乘数 multiplier[0..m-1] </param>
236       ///   <param name="m"> 乘数 multiplier 的精度 </param>
237       ///   <returns> 积 product[0..n+m-1] </returns>
238       public   static   byte [] Multiply( byte [] product,  byte [] multiplicand,  int  n,  byte [] multiplier,  int  m)
239      {
240         int  mn  =  m  +  n, nn  =   1 ;
241        Debug.Assert(product.Length  >=  mn  &&  multiplicand.Length  >=  n  &&  multiplier.Length  >=  m);
242         while  (nn  <  mn) nn  <<=   1 //  为变换找出最小可用的 2 的幂次
243         double [] a  =   new   double [nn  +   1 ], b  =   new   double [nn  +   1 ];
244         for  ( int  i  =   0 ; i  <  n; i ++ ) a[i  +   1 =  multiplicand[i];
245         for  ( int  i  =   0 ; i  <  m; i ++ ) b[i  +   1 =  multiplier[i];
246        RealFFT(a,  false );  //  执行卷积,首先求出二个傅里叶变换
247        RealFFT(b,  false );
248        b[ 1 *=  a[ 1 ];  //  复数相乘的结果(实部和虚部)
249        b[ 2 *=  a[ 2 ];
250         for  ( int  i  =   3 ; i  <=  nn; i  +=   2 )
251        {
252           double  t  =  b[i];
253          b[i]  =  t  *  a[i]  -  b[i  +   1 *  a[i  +   1 ];
254          b[i  +   1 =  t  *  a[i  +   1 +  b[i  +   1 *  a[i];
255        }
256        RealFFT(b,  true );  //  进行傅里叶逆变换
257         byte [] bs  =   new   byte [ nn  +  1 ];
258         long  cy  =   0 //  执行最后完成所有进位的过程
259         for  ( int  i  =  nn, n2  =  nn  /   2 ; i  >=   1 ; i -- )
260        {
261           long  t  =  ( long )(b[i]  /  n2  +  cy  +   0.5 );
262          bs[i]  =  ( byte )(t  %  Base);  //  原书中这句使用循环,有严重的性能问题
263          cy  =  t  /  Base;
264        }
265         if  (cy  >=  Base)  throw   new  OverflowException( " FFT Multiply " );
266        bs[ 0 =  ( byte )cy;
267        Array.Copy(bs, product, n  +  m);
268         return  product;
269      }
270 

接下来的方法是 Multiply,乘法。其算法是使用 RealFFT 求被乘数和乘数的快速傅里叶变换,将结果相乘,然后进行傅里叶逆变换得到卷积,最后执行适当的进位。其原理已经在上一篇随笔“使用快速傅里叶变换计算大整数乘法”中讲述得很清楚了。


271       // = pp.778, mpdiv, 20.6 任意精度的运算
272       ///   <summary>
273       ///  除法。dividend[0..n-1] 除以 divisor[0..m-1],m ≤ n,
274       ///  得到:商 quotient[0..n-m],余数 remainder[0..m-1]。
275       ///   </summary>
276       ///   <param name="quotient"> 商 quotient[0..n-m] </param>
277       ///   <param name="remainder"> 余数 remainder[0..m-1] </param>
278       ///   <param name="dividend"> 被除数 dividend[0..n-1] </param>
279       ///   <param name="n"> 被除数 dividend 的精度 </param>
280       ///   <param name="divisor"> 除数 divisor[0..m-1] </param>
281       ///   <param name="m"> 除数 divisor 的精度 </param>
282       ///   <returns> 商 quotient[0..n-m] </returns>
283       public   static   byte [] DivRem( byte [] quotient,  byte [] remainder,  byte [] dividend,  int  n,  byte [] divisor,  int  m)
264      {
285        Debug.Assert(m  <=  n  &&  dividend.Length  >=  n  &&  divisor.Length  >=  m  &&  quotient.Length  >=  n  -  m  +   1   &&  remainder.Length  >=  m);
286         int  MACC  =   3 ;
287         byte [] s  =   new   byte [n  +  MACC], t  =   new   byte [n  -  m  +  MACC  +  n];
288        Inverse(s, n  -  m  +  MACC, divisor, m);  //  s = 1 / divisor
289        Array.Copy(Multiply(t, s, n  -  m  +  MACC, dividend, n),  1 , quotient,  0 , n  -  m  +   1 );  //  quotient = dividend / divisor
290        Array.Copy(Multiply(t, quotient, n  -  m  +   1 , divisor, m),  1 , s,  0 , n);  //   s = quotient * divisor
291        Subtract(s, dividend, s, n);  //  s = dividend - quotient * divisor
292        Array.Copy(s, n  -  m, remainder,  0 , m);
293         if  (Compare(remainder, divisor, m)  >=   0 //  调整商和余数
294        {
295          Subtract(remainder, remainder, divisor, m);
296          Add(s, quotient, n  -  m  +   1 1 );
297          Array.Copy(s,  1 , quotient,  0 , n  -  m  +   1 );
298        }
299         return  quotient;
300      }
301 
302       // = pp.777 - 778, mpinv, 20.6 任意精度的运算
303       ///   <summary>
304       ///  求倒数。
305       ///   </summary>
306       ///   <param name="inverse"> 倒数 inverse[0..n-1],在 inverse[0] 后有基数的小数点 </param>
307       ///   <param name="n"> 倒数 inverse 的精度 </param>
308       ///   <param name="data"> 被操作数 data[0..m-1],data[0] > 0,在 data[0] 后有基数的小数点 </param>
309       ///   <param name="m"> 被操作数 data 的精度 </param>
310       ///   <returns> 倒数 inverse[0..n-1],在 inverse[0] 后有基数的小数点 </returns>
311       public   static   byte [] Inverse( byte [] inverse,  int  n,  byte [] data,  int  m)
312      {
313        Debug.Assert(inverse.Length  >=  n  &&  data.Length  >=  m);
314        InitialValue(inverse, n, data, m,  false );
315         if  (n  ==   1 return  inverse;
316         byte [] s  =   new   byte [n],  =  new  byte [n  +  n] ;
317        for  (; ; )  //  牛顿迭代法: inverse = inverse * ( 2 - data * inverse )  =>  inverse = 1 / data
318        {
319          Array.Copy(Multiply(t, inverse, n, data, m),  1 , s,  0 , n);  //  s = data * inverse
320          Negative(s, n);                                          //  s = -(data * inverse)
321         s[ 0 -=  ( byte )(Base  -   2 );                              //  s = 2 - data * inverse
322         Array.Copy(Multiply(t, s, n, inverse, n),  1 , inverse,  0 , n);  //  inverse = inverse * s
323         int  i  =   1 ;
324         for  (; i  <  n  -   1   &&  s[i]  ==   0 ; i ++ ) ;  //  判断 s 的小数部分是否为零
325         if  (i  ==  n  -   1 return  inverse;  //  若 s 收敛到 1 则返回 inverse = 1 / data
326       }
327     }
328

 

接下来的方法是 DivRem,除法,以及 Inverse,求倒数。

除法用除数的倒数乘以被除数来计算,倒数值用牛顿迭代法:

Ui+1 = Ui (2 - VUi)

来计算,这导致 U二次收敛于 1/V。实际上,许多超级计算机和 RISC 机都是使用这种迭代法来实现除法的。

要注意在 DivRem 中,求得的余数可能大于等于除数,此时商比实际的值要小一。所以在程序的 293 到 298 行调整商和余数。

 

329      // = pp.778-779, mpsqrt, 20.6 任意精度的运算
330     ///   <summary>
331     ///  求平方根 sqrt,以及平方根的倒数 invSqrt。invSqrt 也可设为 sqrt,此时,invSqrt 也是平方根。
332     ///   </summary>
333     ///   <param name="sqrt"> 平方根 sqrt[0..n-1],在 sqrt[0] 后有基数的小数点 </param>
334     ///   <param name="invSqrt"> 平方根的倒数 invSqrt[0..n-1],在 invSqrt[0] 后有基数的小数点 </param>
335     ///   <param name="n"> 平方根的精度 </param>
336     ///   <param name="data"> 被操作数 data[0..m-1],data[0] > 0,在 data[0] 后有基数的小数点 </param>
337     ///   <param name="m"> 被操作数 data 的精度 </param>
338     ///   <returns> 平方根 sqrt[0..n-1],在 sqrt[0] 后有基数的小数点 </returns>
339     public   static   byte [] Sqrt( byte [] sqrt,  byte [] invSqrt,  int  n,  byte [] data,  int  m)
340     {
341       Debug.Assert(sqrt.Length  >=  n  &&  invSqrt.Length  >=  n  &&  data.Length  >=  m);
342       if  (n  <=   1 throw   new  ArgumentOutOfRangeException( " n " " must greater than 1 " );
343       InitialValue(invSqrt, n, data, m,  true );
344       byte [] s  =   new   byte [n], =  new  byte [n  +  Math.Max(m, n)] ;
345        for  (; ; )  //  invSqrt = invSqrt * (3 - data * invSqrt * invSqrt) / 2 => invSqrt = 1 / sqrt(data)
346       {
347         Array.Copy(Multiply(t, invSqrt, n, invSqrt, n),  1 , s,  0 , n);  //  s = invSqrt * invSqrt
348         Array.Copy(Multiply(t, s, n, data, m),  1 , s,  0 , n);    //  s = data * invSqrt * invSqrt
349         Negative(s, n);                                      //  s = -(data * invSqrt * invSqrt)
350         s[ 0 -=  ( byte )(Base  -   3 );                          //  s = 3 - data * invSqrt * invSqrt
351         Divide(s, s, n,  2 );                               //  s = (3 - data * invSqrt * invSqrt) / 2
352         Array.Copy(Multiply(t, s, n, invSqrt, n),  1 , invSqrt,  0 , n);    //  invSqrt = invSqrt * s
353         int  i  =   1 ;
354         for  (; i  <  n  -   1   &&  s[i]  ==   0 ; i ++ ) ;  //  判断 s 的小数部分是否为零
355         if  (i  <  n  -   1 continue //  若 s 没有收敛到 1 则继续迭代
356         Array.Copy(Multiply(t, invSqrt, n, data, m),  1 , sqrt,  0 , n);  //  sqrt = invSqrt * data = sqrt(data)
357         return  sqrt;
358       }
359     }
360
361     ///   <summary>
362     ///  采用浮点运算以得到一个初始近似值 u[0..n-1]: u = 1 / data 或者 u = 1 / sqrt(data)
363     ///   </summary>
364     ///   <param name="u"> 初始近似值 u[0..n-1] </param>
365     ///   <param name="n"> 所需的精度 </param>
366     ///   <param name="data"> 被操作数 data[0..m-1] </param>
367     ///   <param name="m"> 被操作数 data 的精度 </param>
368     ///   <param name="isSqrt"> 是否求平方根 </param>
369     ///   <returns> 初始近似值 u[0..n-1] </returns>
370     static   byte [] InitialValue( byte [] u,  int  n,  byte [] data,  int  m,  bool  isSqrt)
371     {
372       Debug.Assert(u.Length  >=  n  &&  data.Length  >=  m);
373       int  scale  =   16   /  Len;  //  double 可达到 16 位有效数字
374       double  fu  =   0 ;
375       for  ( int  i  =  Math.Min(scale, m)  -   1 ; i  >=   0 ; i -- ) fu  =  fu  /  Base  +  data[i];
376       fu  =   1   /  (isSqrt  ?  Math.Sqrt(fu) : fu);
377       for  ( int  i  =   0 ; i  <  Math.Min(scale  +   1 , n); i ++ )
378       {
379         int  k  =  ( int )fu;
380         u[i]  =  ( byte )k;
381         fu  =  Base  *  (fu  -  k);
382       }
383       return  u;
384     }
385   }
386 }

最后的方法是 Sqrt,求平方根。用牛顿迭代法计算平方根和除法类似。若:

Ui+1 = Ui (3 - VUi2) / 2

则 U二次收敛于 1/√V,最后乘以 V 就得到√V

 

在上一篇随笔“使用快速傅里叶变换计算大整数乘法”中提到:

由于快速傅里叶变换是采用了浮点运算,因此我们需要足够的精度,以使在出现舍入误差时,结果中每个组成部分的准确整数值仍是可辨认的。长度为 N 的 B 进制数可产生大到 B2N 阶的卷积分量。我们知道,双精度浮点数的尾数是 53 个二进位,所以:

2 x log2B + log2N + 几个 x log2log2N < 53

上式中左边最后一项是为了快速傅里叶变换的舍入误差。

我们假设上式中的“几个”为“三个”,那么,经过简单的计算,得到以下结果:

基数 B长度 N十进制数字个数
256约 107约 2.4 x 107
100约 108约 2 x 108
10约 109约 109

注意,基数 B 选取得越小,计算速度就越慢。


在下一篇随笔中,我将使用 BigArithmetic 类重新实现 Skyiv.Numeric.BigInteger 类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值