银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

最近一段时间,我在 Timus Online Judge 网站做 ACM 题。发现其中不少题目需要用到 BigInteger,例如:

于是,我就开始寻找可以用于 C# 的 BigInteger 类。结果如下表所示:

项目 速度 自动增长 进制 ModPowPowSqrt 源文件大小 备注
Fcl35Biginteger 最快 232 1,755行,47.1 KB 是 struct,不是 class
SkyivBigInteger
109 335行,9.16 KB 源文件短小精悍,功能全
ChewBigInteger
232 3,335行,102 KB 源文件中有大量的注释
JavaBigInteger 极慢 28? (vjslib.dll)
需要 J# run-time library

在上表中,JavaBigInteger 是指 java.math.BigInteger,她在 vjslib.dll 中,需要 J# run-time library (Visual Studio 2005/2008 中已经包括 J# run-time library)。用 .NET Reflector 打开 C:\Windows\Microsoft.NET\Framework\v2.0.50727\vjslib.dll 文件,找到 java.math.BigInteger,查看其源程序代码如下:

1 [Serializable, JavaInterfaces("2;System/IConvertible;java/lang/Comparable;")]
2 public class BigInteger : Number, IConvertible, Comparable
3 {
4   // Fields
5   private BigInteger __absOfthis;
6   [JavaFlags(0x1002)]
7   internal sbyte[] __bits;
8   // 
9 }

根据第 7 行的 internal sbyte[] __bits; 猜想 java.math.BigInteger 的内部实现是基于 28 进制的。


ChewBigInteger 就是 Chew Keong TANCodeProject 中的 C# BigInteger Class 。这是个很有名的 C# Biginteger 类,它写于2002年8月到9月,已经有很久的历史了。但是她有个致命的缺点,就是不能自动增长,需要事先指定最大允许有多大的数,如果在运算过程中产生超过你事先指定大小的数,就会引发异常。

她的部分源程序代码如下所示:

 1 namespace ChewKeongTAN
 2 {
 3   /*public */class BigInteger
 4   {
 5     // maximum length of the BigInteger in uint (4 bytes)
 6     // change this to suit the required level of precision.
 7 
 8     private const int maxLength = 26591// == Math.Ceiling ( digits / log10(2^32) )
 9 
10     private uint[] data = null;          // stores bytes from the Big Integer
11     public int dataLength;               // number of actual chars used
12     
13     // 
14     
15   }
16 }

她内部使用一个 uint[] 来存储,是基于 232 ≈ 4.3 x 109 进制的。需要事先设定 maxLength 的值(程序中第 8 行),用以指定最多允许多少个 232 进制的数字,换算用的公式是:

maxLength = Math.Ceiling ( digits / log10(232) ) ≈ digits / 9.6

上式中 digits 表示十进制数字的个数。


Fcl35Biginteger 就是 .NET Framework 3.5 Base Class Library 中的 System.Numeric.BigInteger,她在 System.Core.dll 中。注意,她是一个 internal struct。首先,她是一个 struct,不是一个 class,也就是说她是值类型而不是引用类型。其次,致命的是,她是 internal 的,而不是 public 的,也就是说,她就在那里,但是你就是不能使用她。实际上,在 .NET Framework 3.5 的 Beta 和 latest CTP 版本中,她还是 public 的,只不过是在 .NET Framework 3.5 正式发布时变成了 internal 的。这里有一篇 BCL Team 的 Melitta Andersen 于2008年1月4日发表的 Blog: Where did BigInteger go? 谈到这件事,要点是:

So why was BigInteger cut?  The basic rationale behind making BigInteger internal was that it just wasn't ready to ship.  We thought our implementation met the needs for a BigInteger type.  But then we had some other teams take a look at it and they pointed out some performance and compatibility issues that we just didn't have time to fix before we shipped.

于是, .NET Reflector 又上场了,用她打开 C:\Windows\assembly\GAC_MSIL\System.Core\3.5.0.0__b77a5c561934e089\System.Core.dll 文件,找到 System.Numeric.BigInteger,在 Disassembler 窗口中,点击最下方的 Expand Methods,然后把源程序代码全部复制到 Visual Studio 2008 的 IDE 窗口(注意,目标 Framework 设定为 .NET Framework 2.0)中。按下编译按钮,天啊,出现了好多错误。于是,就开始修改源程序代码:

  • 实现两个空的 sealed class: ImmutableAttribute 和 PureAttribute。(前一个好理解,后一个不知是什么东东。当然,实际上她们不应该是空的)
  • 实现一个 static class: Contract,其中包括两个重载的 public static void Requires 方法。(当然,实际上的 Contract.Requires 应该更复杂)
  • 实现一个 static class: Res,其中包括若干个 public static readonly string 字段。(当然,实际上 Res 应该从资源文件中获得本地化的数据)
  • 将程序中出现的某些常数值还原为 private const 字段。注意,这不是必须的,只是为了使程序更容易看懂。如果不做这一步,就可以将程序中所有的 private const 字段全部删除。
  • 修改程序中某些变量的名称。注意,这也不是必须的,也只是为了使程序更容易看懂。

然后,再次编译,耶,通过了。但是,还是高兴得太早了一点,运行一些测试,发现答案根本不对。

经检查,发现是 .NET Reflector 在反汇编为 C# 代码时会丢失一些强制类型转换,这在我的上一篇随笔 浅谈 Math.BigMul 方法 中已经提到过了:她把 Math.BigMul 方法中的唯一的语句 return (long)a * b; 错误地反汇编为 return (a * b); 了,但是反汇编为 IL 代码是正确的。

于是,只好逐条语句检查,如果需要进行强制类型转换,就自己加上,同时还去掉一些不必要的强制类型转换和括号,以便使源程序代码更易读。真是一个很烦人和困难和工作,热切盼望 .NET Reflector 能够尽快修复这个 BUG。:)

好了,经过调试,程序终于运行正常了。下面就是修改后的部分源程序代码:

 1 namespace Fcl35.Numeric
 2 {
 3   [Serializable, StructLayout(LayoutKind.Sequential), Immutable, ComVisible(false)]
 4   public /*internal*/ struct BigInteger : IFormattable,
 5     IEquatable<BigInteger>, IComparable<BigInteger>, IComparable
 6   {
 7     private const ulong Base = uint.MaxValue + 1UL// 0x100000000L;
 8     private readonly short _sign;
 9     private readonly uint[] _data;
10     private int _length;
11     // 
12   }
13 }

ChewKeongTAN.BigInteger 类一样,Fcl35.Numberic.BigInteger 结构内部也使用一个 uint[] 来存储,同样也是基于 232 ≈ 4.3 x 109 进制的。但是,最重要的一点,Fcl35.Numberic.BigInteger 结构是动态增长的,不需要事先指定最大允许有多大的数。


现在来讨论我最近写的一个 Skyiv.Numeric.BigInteger 类,她是从我在做 ACM 题时写的极简单的如下所示的 BigInteger 类发展而来的:

1 sealed class BigInteger
2 {
3   int[] digits = new int[1800];
4   // 
5 }

这个极简单的 BigInteger 类的全部源程序代码可以在本随笔开头给出的 URL 中找到,只有五十多行。她是基于 10 进制的,内部使用一个 int[] 来存储,需要事先指定该数组的大小,不能动态增长,而且只能表示非负整数。 

下面是 Skyiv.Numeric.BigInteger 的源程序代码,非常短小精悍,只有区区 335 行,该有的功能基本上都有了。实现了基本的算术运算和逻辑运算,还实现了 Pow 和 Sqrt 方法。

  1 using System;
  2 using System.Text;
  3 using System.Collections.Generic;
  4 
  5 namespace Skyiv.Numeric
  6 {
  7   sealed class BigInteger : IEquatable<BigInteger>, IComparable<BigInteger>
  8   {
  9     static readonly int Len = 9// int.MaxValue = 2,147,483,647
 10     static readonly int Base = (int)Math.Pow(10, Len);
 11 
 12     int sign;
 13     List<int> data;
 14 

她内部使用一个 List<int> 来存储(程序中第 13 行),是动态增长的,基 于 109 进制(第 9 和 10 行)。

 15     BigInteger(long x)
 16     {
 17       sign = (x == 0? 0 : ((x > 0? 1 : -1);
 18       data = new List<int>();
 19       for (ulong z = Abs(x); z != 0; z /= (ulong)Base) data.Add((int)(z % (ulong)Base));
 20     }
 21 
 22     BigInteger(BigInteger x)
 23     {
 24       sign = x.sign;  // x != null
 25       data = new List<int>(x.data);
 26     }
 27 
 28     public static implicit operator BigInteger(long x)
 29     {
 30       return new BigInteger(x);
 31     }
 32 

她只有两个构造函数,而且都是 private 的。要获得该类的实例,主要途径之一是通过从 long、int 等类型隐式转换(第 28 到 31 行)而来,例如 BigInteger a = 2; 语句。

 33     public static BigInteger Parse(string s)
 34     {
 35       if (s == nullreturn null;
 36       s = s.Trim().Replace(","null);
 37       if (s.Length == 0return 0;
 38       BigInteger z = 0;
 39       z.sign = (s[0== '-'? -1 : 1;
 40       if (s[0== '-' || s[0== '+') s = s.Substring(1);
 41       int r = s.Length % Len;
 42       z.data = new List<int>(new int[s.Length / Len + ((r != 0? 1 : 0)]);
 43       int i = z.data.Count - 1;
 44       if (r != 0) z.data[i--= int.Parse(s.Substring(0, r));
 45       for (; i >= 0; i--, r += Len) z.data[i] = int.Parse(s.Substring(r, Len));
 46       z.Shrink();
 47       return z;
 48     }
 49 

获得该类的实例的主要途径之二是调用静态的 Parse 方法,例如:var a = BigInteger.Parse("-123,456,789"); 语句。

 50     public static void Swap(ref BigInteger x, ref BigInteger y)
 51     {
 52       BigInteger z = x;
 53       x = y;
 54       y = z;
 55     }
 56 
 57     public static ulong Abs(long x)
 58     {
 59       return (x < 0? (ulong)-x : (ulong)x;
 60     }
 61 
 62     public static BigInteger Abs(BigInteger x)
 63     {
 64       if (x == nullreturn null;
 65       BigInteger z = new BigInteger(x);
 66       z.sign = Math.Abs(x.sign);
 67       return z;
 68     }
 69 

上面三个静态的公共方法都很简单,主要是为了方便,如果不提供这些方法也没关系。要注意的是第二个 Abs(long) 方法返回 ulong 类型,而且从不抛出异常。而 Math.Abs(long) 返回 long 类型,而且当参数为 long.MinValue 时会抛出异常。

 70     public static BigInteger Pow(BigInteger x, int y)
 71     {
 72       if (x == nullreturn null;
 73       BigInteger z = 1, n = x;
 74       for (; y > 0; y >>= 1, n *= n) if ((y & 1!= 0) z *= n;
 75       return z;
 76     }
 77 
 78     public static BigInteger operator +(BigInteger x)
 79     {
 80       if (x == nullreturn null;
 81       return new BigInteger(x);
 82     }
 83 
 84     public static BigInteger operator -(BigInteger x)
 85     {
 86       if (x == nullreturn null;
 87       BigInteger z = new BigInteger(x);
 88       z.sign = -x.sign;
 89       return z;
 90     }
 91 

注意,重载的单目 + 操作符不仅是为了和重载的单目 - 操作符对应,而且可以用于克隆。 BigInteger x = +y; 和 BigInteger x = y; 语句是完全不同的,前者为克隆一份新的对象,后者只是直接引用同一对象。之所以不实现 IClone 接口,是因为该接口的 Clone 方法的返回类型是 object,使用起来很不方便:var x = y.Clone() as BigInteger; 需要一次强制类型转换。如果 FCL 中有泛型版本的 IClone<T> 接口就好了。

 92     public static BigInteger operator ++(BigInteger x)
 93     {
 94       return x + 1;
 95     }
 96 
 97     public static BigInteger operator --(BigInteger x)
 98     {
 99       return x - 1;
100     }
101 

在 C# 语言中重载 ++ 和 -- 运算符比在 C++ 语言中容易,C# 编译器会处理好前缀和后缀的情况。

102     public static BigInteger operator +(BigInteger x, BigInteger y)
103     {
104       if (x == null || y == nullreturn null;
105       if (x.AbsCompareTo(y) < 0) Swap(ref x, ref y);
106       BigInteger z = new BigInteger(x);
107       if (x.sign * y.sign == -1) z.AbsSubtract(y);
108       else z.AbsAdd(y);
109       return z;
110     }
111 
112     public static BigInteger operator -(BigInteger x, BigInteger y)
113     {
114       if (x == null || y == nullreturn null;
115       return x + (-y);
116     }
117 
118     public static BigInteger operator *(BigInteger x, BigInteger y)
119     {
120       if (x == null || y == nullreturn null;
121       BigInteger z = 0;
122       z.sign = x.sign * y.sign;
123       z.data = new List<int>(new int[x.data.Count + y.data.Count]);
124       for (int i = x.data.Count - 1; i >= 0; i--)
125         for (int j = y.data.Count - 1; j >= 0; j--)
126         {
127           long n = Math.BigMul(x.data[i], y.data[j]);
128           z.data[i + j] += (int)(n % Base);
129           z.CarryUp(i + j);
130           z.data[i + j + 1+= (int)(n / Base);
131           z.CarryUp(i + j + 1);
132         }
133       z.Shrink();
134       return z;
135     }
136 
137     public static BigInteger operator /(BigInteger dividend, BigInteger divisor)
138     {
139       BigInteger remainder;
140       return DivRem(dividend, divisor, out remainder);
141     }
142 
143     public static BigInteger operator %(BigInteger dividend, BigInteger divisor)
144     {
145       BigInteger remainder;
146       DivRem(dividend, divisor, out remainder);
147       return remainder;
148     }
149 

在 C# 中重载加减乘除等运算符,C# 编译器也会自动添上相应的赋值运算符(+=, -=, ...)。

150     public static BigInteger DivRem(BigInteger dividend, BigInteger divisor, out BigInteger remainder)
151     {
152       remainder = null;
153       if (dividend == null || divisor == nullreturn null;
154       if (divisor.sign == 0throw new Exception("division by zero");
155       if (dividend.AbsCompareTo(divisor) < 0)
156       {
157         remainder = new BigInteger(dividend);
158         return 0;
159       }
160       remainder = 0;
161       BigInteger quotient = 0;
162       quotient.sign = dividend.sign * divisor.sign;
163       quotient.data = new List<int>(new int[dividend.data.Count]);
164       divisor = Abs(divisor); // NOT: divisor.sign = Math.Abs(divisor.sign);
165       for (int i = dividend.data.Count - 1; i >= 0; i--)
166       {
167         remainder = remainder * Base + dividend.data[i];
168         int iQuotient = remainder.BinarySearch(divisor, -1);
169         quotient.data[i] = iQuotient;
170         remainder -= divisor * iQuotient;
171       }
172       quotient.Shrink();
173       if (remainder.sign != 0) remainder.sign = dividend.sign;
174       return quotient;
175     }
176 
177     public static BigInteger Sqrt(BigInteger x)
178     {
179       if (x == null || x.sign < 0return null;
180       BigInteger root = 0;
181       root.sign = 1;
182       root.data = new List<int>(new int[x.data.Count / 2 + 1]);
183       for (int i = root.data.Count - 1; i >= 0; i--) root.data[i] = x.BinarySearch(root, i);
184       root.Shrink();
185       return root;
186     }
187 
188     int BinarySearch(BigInteger divisor, int i)
189     {
190       int low = 0, high = Base - 1, mid = 0, cmp = 0;
191       while (low <= high)
192       {
193         mid = (low + high) / 2;
194         cmp = CompareTo(divisor, mid, i);
195         if (cmp > 0) low = mid + 1;
196         else if (cmp < 0) high = mid - 1;
197         else return mid;
198       }
199       return (cmp < 0? (mid - 1) : mid;
200     }
201 
202     int CompareTo(BigInteger divisor, int mid, int i)
203     {
204       if (i >= 0) divisor.data[i] = mid;
205       return AbsCompareTo(divisor * ((i >= 0? divisor : mid));
206     }
207 
208     void AbsAdd(BigInteger x)
209     {
210       for (int i = 0; i < x.data.Count; i++)
211       {
212         data[i] += x.data[i];
213         CarryUp(i);
214       }
215     }
216 
217     void AbsSubtract(BigInteger x)
218     {
219       for (int i = 0; i < x.data.Count; i++)
220       {
221         data[i] -= x.data[i];
222         CarryDown(i);
223       }
224       Shrink();
225     }
226 
227     void CarryUp(int n)
228     {
229       for (; data[n] >= Base; n++)
230       {
231         if (n == data.Count - 1) data.Add(data[n] / Base);
232         else data[n + 1+= data[n] / Base;
233         data[n] %= Base;
234       }
235     }
236 
237     void CarryDown(int n)
238     {
239       for (; data[n] < 0; n++)
240       {
241         data[n + 1]--;
242         data[n] += Base;
243       }
244       Shrink();
245     }
246 
247     void Shrink()
248     {
249       for (int i = data.Count - 1; i >= 0 && data[i] == 0; i--) data.RemoveAt(i);
250       if (data.Count == 0) sign = 0;
251     }
252 

除法、取模和求平方根都是通过二分搜索来进行的,每次搜索时都有进行一次乘法和比较运算,以决定下次要搜索的区间。

253     public static bool operator ==(BigInteger x, BigInteger y)
254     {
255       if (object.ReferenceEquals(x, null)) return object.ReferenceEquals(y, null);
256       return x.Equals(y);
257     }
258 
259     public static bool operator !=(BigInteger x, BigInteger y)
260     {
261       if (object.ReferenceEquals(x, null)) return !object.ReferenceEquals(y, null);
262       return !x.Equals(y);
263     }
264 
265     public static bool operator <(BigInteger x, BigInteger y)
266     {
267       if (object.ReferenceEquals(x, null)) return !object.ReferenceEquals(y, null);
268       return x.CompareTo(y) < 0;
269     }
270 
271     public static bool operator >(BigInteger x, BigInteger y)
272     {
273       if (object.ReferenceEquals(x, null)) return false;
274       return x.CompareTo(y) > 0;
275     }
276 
277     public static bool operator <=(BigInteger x, BigInteger y)
278     {
279       if (object.ReferenceEquals(x, null)) return true;
280       return x.CompareTo(y) <= 0;
281     }
282 
283     public static bool operator >=(BigInteger x, BigInteger y)
284     {
285       if (object.ReferenceEquals(x, null)) return object.ReferenceEquals(y, null);
286       return x.CompareTo(y) >= 0;
287     }
288 
289     public override string ToString()
290     {
291       StringBuilder sb = new StringBuilder();
292       if (sign < 0) sb.Append('-');
293       sb.Append((data.Count == 0? 0 : data[data.Count - 1]);
294       for (int i = data.Count - 2; i >= 0; i--) sb.Append(data[i].ToString("D" + Len));
295       return sb.ToString();
296     }
297 
298     public override int GetHashCode()
299     {
300       int hash = sign;
301       foreach (int n in data) hash ^= n;
302       return hash;
303     }
304 
305     public override bool Equals(object other)
306     {
307       if (other == null || GetType() != other.GetType()) return false;
308       return Equals(other as BigInteger);
309     }
310 
311     public bool Equals(BigInteger other)
312     {
313       return CompareTo(other) == 0;
314     }
315 
316     public int CompareTo(BigInteger other)
317     {
318       if (object.ReferenceEquals(other, null)) return 1;
319       if (sign < other.sign) return -1;
320       if (sign > other.sign) return 1;
321       if (sign == 0return 0;
322       return sign * AbsCompareTo(other);
323     }
324 
325     int AbsCompareTo(BigInteger other)
326     {
327       if (data.Count < other.data.Count) return -1;
328       if (data.Count > other.data.Count) return 1;
329       for (int i = data.Count - 1; i >= 0; i--)
330         if (data[i] != other.data[i])
331           return (data[i] < other.data[i]) ? -1 : 1;
332       return 0;
333     }
334   }
335 }

最后就是各种逻辑运算符以及从基类继承的 ToString、GetHashCode、Equals 方法,以及用于实现接口的 Equals 和 CompareTo 方法。


下面,让我们来比较这四种 BigInteger 的效率吧。测试程序如下所示:

TestMain.cs:

 1 using System;
 2 
 3 namespace Skyiv
 4 {
 5   class TestMain
 6   {
 7     static void Main()
 8     {
 9       string s1 = new TestFcl35BigInteger().Run(20);
10       string s2 = new TestSkyivBigInteger().Run(20);
11       string s3 = new TestChewBigInteger().Run(20);
12       string s4 = new TestJavaBigInteger().Run(14);
13       Console.WriteLine(s1 == s2 && s1 == s3);
14     }
15   }
16 }

TestBase.cs:

 1 using System;
 2 using System.Diagnostics;
 3 
 4 namespace Skyiv
 5 {
 6   class TestBase
 7   {
 8     public string Run(int n)
 9     {
10       var stopWatch = Stopwatch.StartNew();
11       var s = RunTest(n);
12       stopWatch.Stop();
13       Console.WriteLine("{0,-25} {1,11:F7} {2,7:N0}",
14         GetType(), stopWatch.Elapsed.TotalSeconds, s.Length);
15       return s;
16     }
17 
18     protected virtual string RunTest(int n)
19     {
20       throw new InvalidOperationException("TestBase.RunTest()");
21     }
22   }
23 }

Test?????BigInteger.cs (以 TestSkyivBigInteger 为例,其它的大同小异):

 1 using System;
 2 using BigInteger = Skyiv.Numeric.BigInteger;
 3 
 4 namespace Skyiv
 5 {
 6   sealed class TestSkyivBigInteger : TestBase
 7   {
 8     protected sealed override string RunTest(int n)
 9     {
10       BigInteger a = 2, b;
11       for (int i = 2; i <= n; i++, a *= b) b = a + i;
12       return a.ToString();
13     }
14   }
15 }

这个测试程序主要是测试 BigInteger 的加法和乘法运算,其运行结果如下:

Skyiv.TestFcl35BigInteger  25.8100647 256,142
Skyiv.TestSkyivBigInteger  34.2497761 256,142
Skyiv.TestChewBigInteger  396.8586529 256,142
Skyiv.TestJavaBigInteger   61.9083458   4,003
True

上表中,第二栏是测试程序的运行时间,单位是秒。第三栏是最终运算结果的十进制数字的个数。前三个 BigInteger 都使用了同一测试用例,运算结果达 256,142 个十进制数字,并且经过比较这三个 BigInteger 的运算结果是相同的。最后的 java.math.BigInteger 由于运算速度极慢(估计是微软 J# 的问题,我想在真正的 java 语言中应该是比较快的),测试用例的规模更小,运算结果只有 4,003 个十进制数字。从测试结果可以看出:

  • Fcl35.Numeric.BigInteger 最快,推荐使用。
  • Skyiv.Numeric.BigInteger 也很快,并且源程序代码短小精悍,也推荐使用。
  • ChewKeongTAN.BigInteger 很慢,并且不能动态增长,不推荐使用。
  • java.math.BigInteger 极慢,并且需要 J# run-time library,也不推荐使用。

Skyiv.Numeric.BigInteger 和 ChewKeongTAN.BigInteger 的乘法都是使用简单的乘法,时间复杂度是 O(N2)。Fcl35.Numeric.BigInteger 的乘法是使用 Karatsuba 算法,其时间复杂度约为 O(N1.585),如下所示:

 1 public static BigInteger Multiply(BigInteger x, BigInteger y)
 2 {
 3   int xLength = x.Length;
 4   int yLength = y.Length;
 5   if ((((xLength + yLength) >= UpperBoundForSchoolBookMultiplicationDigits /* 0x40 */)
 6     && (xLength >= ForceSchoolBookMultiplicationThresholdDigits /* 8 */))
 7     && (yLength >= ForceSchoolBookMultiplicationThresholdDigits /* 8 */))
 8   {
 9     return MultiplyKaratsuba(x, y);
10   }
11   return MultiplySchoolBook(x, y);
12 }
13 
14 private static BigInteger MultiplyKaratsuba(BigInteger x, BigInteger y)
15 {
16   int length = Math.Max(x.Length, y.Length) / 2;
17   if (length <= 2 * ForceSchoolBookMultiplicationThresholdDigits /* 0x10 */
18     || x.Length < 2 * ForceSchoolBookMultiplicationThresholdDigits /* 0x10 */
19     || y.Length < 2 * ForceSchoolBookMultiplicationThresholdDigits /* 0x10 */)
20     return MultiplySchoolBook(x, y);
21   int shift = BitsPerDigit /* 0x20 */ * length;
22   BigInteger i1 = RightShift(x, shift);
23   BigInteger i2 = x.RestrictTo(length);
24   BigInteger i3 = RightShift(y, shift);
25   BigInteger i4 = y.RestrictTo(length);
26   BigInteger i5 = Multiply(i1, i3);
27   BigInteger i6 = Multiply(i2, i4);
28   BigInteger i7 = Multiply(i1 + i2, i3 + i4) - (i5 + i6);
29   return (i6 + LeftShift(i7 + LeftShift(i5, shift), shift));
30 }

其实如果使用快速傅里叶变换(FFT)来进行乘法运算,时间复杂度可降低到 O(N logN loglogN)。而且除法可以用除数的倒数乘以被除数来计算,倒数值用牛顿迭代法:

Ui+1 = Ui (2 - VUi)

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

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

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

我可能会使用快速傅里叶变换来改写 Skyiv.Numeric.BigInteger 类。微软声称在下一版本的 .NET Framework 中会重新发布 System.Numeric.BigInteger,希望也使用快速傅里叶变换。:)

 

本文中的所有源代码可以到 http://bitbucket.org/ben.skyiv/biginteger/downloads 页面下载。
也可以使用 hg clone http://bitbucket.org/ben.skyiv/biginteger/ 命令下载。
关于 hg ,请参阅 Mercurial 备忘录

 


  1. 2008-07-25 更新: 请参阅:再谈 BigInteger - 使用快速傅里叶变换
  2. 2010-04-22 更新: 对 Skyiv.Numeric.BigInteger 的 GetHashCode、Parse 和 ToString 方法进行了优化。请参阅:再谈 BigInteger - 优化
posted on 2008-07-13 21:25  银河  阅读(22680)  评论(22编辑  收藏  举报