我们一直都知道,由于计算机变量类型的限制,数学阶乘的计算受到了限制。在VB6系统中,Long数据类型存放数据的范围是:-2147483648到+2147483647,实践证明最多可以计算到12的阶乘。当计算到13!的时候,系统就会提示溢出错误。即使到了VB.NET时代,我先后在VB.NET2005和VB.NET2010中尝试,表明Long数据类型变量已经采用了64位(8字节)内存存放数据,尽管值的范围提升到了 -9,223,372,036,854,775,808 到9,223,372,036,854,775,807,但是也最多可以计算出20的阶乘,结果为20!=2432902008176640000。
于是一个问题摆在我们面前,有没有方法,使用现有的系统来完成100的阶乘的计算呢,回答是肯定的。解决的基本思路简述为:把大整数转换成字符串来存放,当需要运算的时候,把字符串分割成一定位数的字符串,分别转换成数值进行运算,运算完成后再转换成字符串,把这些分割的字符串再连接起来,恢复到一个整体作为运算结果保存、输出。
一、有关大整数的说明:
这里所谓的大整数,就是指数值大小超过VB系统中LONG类型变量范围的正整数。由于本文只涉及到正整数,所以仅就正整数进行研究讨论。
二、计算两个大整数的和:
下面就一个具体例子,加以说明,计算:
9876543210 + 246899246899 = ?
为了用计算机实现计算,我们不妨通过大家熟悉的笔算的过程,来分析寻找如何用计算机模拟实现。
9876543210
+ 246899246899
-------------------
256775790109
通常我们是从低位开始,采用同位数字相加,并计算是否有进位,这样逐位的由低向高,来完成加法计算的。过程如下:
1. 首先计算个位0 + 9 =
9,本位得到结果为9,并且没有发生进位,进位数字为0。
2. 计算十位1 + 9 +
0(进位数字0) =
10,本位得到结果为0,并且没有发生进位,进位数字为1。
3. 计算百位2 + 8 +
1(进位数字1) =
10,本位得到结果为1,进位数字为1。
4. 计算千位3 + 6 +
1(进位数字1) =
10,本位得到结果为0,进位数字为1。
5. 计算万位4 + 4 +
1(进位数字1) =
9,本位得到结果为9,进位数字为0。
6. 计算十万位5 + 2 +
0(进位数字0) =
7,本位得到结果为7,进位数字为0。
7. 计算百万位6 + 9 +
0(进位数字0) =
15,本位得到结果为5,进位数字为1。
8. 计算千万位7 + 9 +
1(进位数字1) =
17,本位得到结果为7,进位数字为1。
9. 计算亿位8 + 8 +
1(进位数字1) =
17,本位得到结果为7,进位数字为1。
10. 计算十亿位9 + 6 +
1(进位数字1) =
16,本位得到结果为6,进位数字为1。
11. 计算百亿位0 + 4 +
1(进位数字1) =
5,本位得到结果为5,进位数字为0。
12. 计算千亿位0 + 2 +
0(进位数字0) =
2,本位得到结果为2,进位数字为0。
这样得到了结果为:256775790109
为了用程序代码来模拟人的笔算过程,采用的算法如下:
首先把这两个加数,分别作为字符串存放到变量中;
9876543210 ==> MyLs1 = "9876543210"
246899246899 ==> MyLs2 =
"246899246899"
为了符合计算机程序要求,先做预处理,在位数少的字符串左边添加"0",使得便于程序实现,结果如下:
MyLs1 = "009876543210"
MyLs2 = "246899246899"
这样就可以使用循环,来实现模拟人的笔算过程了,完整的代码如下:
'自定义函数--对两个数字字符串,实现算术加法运算,返回它们和的数字字符串
Private Function Myaddition(ByVal s1 As String,
ByVal s2 As String) As String
Dim i As Integer
Dim L As Integer
Dim LStr As String
Dim Ls1 As String
Dim Ls2 As String
Dim MyLs1 As String
Dim MyLs2 As String
Dim MyResult As String
Dim CarryBit As String
'数据预处理,去除高位上的"0"
'防止s1="001",s2="000"等情况,使得转换成s1="1",s2="0"。
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
L = 0
For i = 1 To Len(s1)
If Mid(s1, i,
1) <> "0" Then
L = i
Exit For
End
If
Next i
If L <> 0 Then
Ls1 = Mid(s1,
L)
Else
Ls1 =
"0"
End If
L = 0
For i = 1 To Len(s2)
If Mid(s2, i,
1) <> "0" Then
L = i
Exit For
End
If
Next i
If L <> 0 Then
Ls2 = Mid(s2,
L)
Else
Ls2 =
"0"
End If
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'把两个加数字符串赋予变量MyLs1,MyLs2
'并对位数少的那个字符串,在其左面添加字符"0",使得两个字符串长度相等
If Len(Ls1) >= Len(Ls2) Then
MyLs1 =
Ls1
MyLs2 =
String(Len(Ls1) - Len(Ls2), "0") & Ls2
Else
MyLs1 =
String(Len(Ls2) - Len(Ls1), "0") & Ls1
MyLs2 =
Ls2
End If
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'开始用循环完成模拟笔算的加法运算
CarryBit =
"0" '存放进位数,初始为0
MyResult =
"" '存放累加和的结果
'循环从低位(个位)开始逐位向高位模拟笔算,进行加法计算
For i = Len(MyLs1) To 1 Step -1
'计算某一位数字的和,同时加上低位运算的进位
LStr =
CStr(Val(Mid(MyLs1, i, 1)) + Val(Mid(MyLs2, i, 1)) +
Val(CarryBit))
'针对本次是否有进位的两种不同情况,实现累加和
If Len(LStr)
= 1 Then
'没有进位
MyResult = LStr &
MyResult '累加本位
CarryBit =
"0" '进位置0
Else
'有进位
MyResult = Right(LStr, 1) &
MyResult '累加本位
CarryBit = Left(LStr,
1) '存储进位
End
If
Next i
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'这里注意:增加下面一个判断的用意,是最高一位运算结果有可能出现进位
'所以要根据最高一位运算结果是否有进位,如果有进位要增加这一位
If CarryBit <> "0" Then
MyResult =
CarryBit & MyResult
End If
'返回函数值
Myaddition = MyResult
End Function
三,计算两个大整数的积:
下面也用一个具体例子,加以说明,计算:
998877665544332211 × 7878 = ?
为了用计算机实现计算,我们不妨同样通过大家熟悉的笔算的过程,来分析寻找如何用计算机模拟实现。
998877665544332211
× 7878
----------------------------
7991021324354657688 ==> d(1)
6992143658810325477 ==> d(2)
7991021324354657688 ==> d(3)
6992143658810325477 ==> d(4)
-------------------------------------
求和 = d(1) + d(2) + d(3) +
d(4)
笔算的方法就是最后,把得到的四个积,再求和就是最终的结果。上述红色文字部分,是我设想用代码来求和,就是把这四个积转换成字符串分别保存到变量,然后调用前面给出的求和函数,进行一一求和,最终获得乘积的结果。
分析:与加法不同,这里需要先用下面一个数的个位8去乘以998877665544332211,可以用循环来实现,模拟人的笔算过程(从右往左,从低位到高位);
1. 计算1×8 +
0(初始化进位为0)
= 8,本位得到结果为8,进位数字为0。
2. 计算1×8 +
0(上次计算进位为0)
= 8,本位得到结果为8,进位数字为0。
3. 计算2×8 +
0(上次计算进位为0)
= 16,本位得到结果为6,进位数字为1。
4. 计算2×8 +
1(上次计算进位为1)
= 17,本位得到结果为7,进位数字为1。
5. 计算3×8 +
1(上次计算进位为1)
= 25,本位得到结果为5,进位数字为2。
6. 计算3×8 +
2(上次计算进位为2)
= 26,本位得到结果为6,进位数字为2。
7. 计算4×8 +
2(上次计算进位为2)
= 34,本位得到结果为4,进位数字为3。
8. 计算4×8 +
3(上次计算进位为3)
= 35,本位得到结果为5,进位数字为3。
9. 计算5×8 +
3(上次计算进位为3)
= 43,本位得到结果为3,进位数字为4。
10. 计算5×8 +
4(上次计算进位为4)
= 44,本位得到结果为4,进位数字为4。
11. 计算6×8 +
4(上次计算进位为4)
= 52,本位得到结果为2,进位数字为5。
12. 计算6×8 +
5(上次计算进位为5)
= 53,本位得到结果为3,进位数字为5。
13. 计算7×8 +
5(上次计算进位为5)
= 61,本位得到结果为1,进位数字为6。
14. 计算7×8 +
6(上次计算进位为6)
= 62,本位得到结果为2,进位数字为6。
15. 计算8×8 +
6(上次计算进位为6)
= 70,本位得到结果为0,进位数字为7。
16. 计算8×8 +
7(上次计算进位为7)
= 71,本位得到结果为1,进位数字为7。
17. 计算9×8 +
7(上次计算进位为7)
= 79,本位得到结果为9,进位数字为7。
18. 计算9×8 +
7(上次计算进位为7)
= 79,本位得到结果为9,进位数字为7。
得到:991021324354657688,由于最后一次乘积还有进位数字7,所以不能忽略,最终结果是:7991021324354657688。注意:上述四个积中,d(2)尾部要添加1个0,
d(3)尾部要添加2个0,
d(4)尾部要添加3个0,一般地:d(n)尾部要添加n-1个0。
这样的计算,可以用循环来实现,考虑到两个乘数都是多位数,所以需要用双重循环来求得上述4个积,另外考虑,事前无法知道数字的具体位数,所以采用动态数组来实现存放每次得到的积。可以看到上述18步骤中每一部,是求得乘积后,再进行加法运算,这里的加法运算,可以调用前面的自定义函数。同样四次积求和也需要调用该函数。
下面给出完整的乘法代码如下:
'自定义函数--对两个数字字符串,实现算术乘积运算,返回它们和的数字字符串
Private Function MyProduct(ByVal s1 As String,
ByVal s2 As String) As String
Dim i As Integer
Dim J As Integer
Dim LStr As String
Dim MyLs1 As String
Dim MyLs2 As String
Dim MyResult As String
Dim CarryBit As String
'声明动态数组,存放乘积
Dim d() As String
Dim n As
Integer '用来控制动态数组的规模
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'把位数多的存放在MyLs1,这样循环次数少,得到的积的个数也少
If Len(s1) > Len(s2) Then
MyLs1 =
s1
MyLs2 =
s2
Else
MyLs1 =
s2
MyLs2 =
s1
End If
n =
0 '动态数组元素个数初始化0
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'循环从低位(个位)开始逐位向高位模拟笔算,进行乘法计算
For i = Len(MyLs2) To 1 Step -1
CarryBit =
"0" '存放进位数,初始为0
MyResult =
"" '存放乘积的结果
'用位数少的那个字符串,从低位开始逐位与位数多的那个字符串相乘
For J =
Len(MyLs1) To 1 Step -1
'计算某一位数字的乘积,同时加上低位运算的进位
LStr = CStr(Val(Mid(MyLs2, i, 1)) * Val(Mid(MyLs1, J,
1)))
'因为要与进位求和,调用求和函数
LStr = Myaddition(LStr, CarryBit)
'针对本次是否有进位的两种不同情况,实现累加和
If Len(LStr) = 1 Then
'没有进位
MyResult = LStr &
MyResult '累加本位
CarryBit =
"0" '进位置0
Else
'有进位
MyResult = Right(LStr, 1) &
MyResult '累加本位
CarryBit = Left(LStr,
1) '存储进位
End If
Next
J
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'考虑最高一位运算结果可能有进位
If CarryBit
<> "0" Then
MyResult = CarryBit & MyResult
End
If
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'动态数组D的规模增加1
n = n +
1
ReDim
Preserve d(n)
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'在末尾添加字符“0”
MyResult =
MyResult & String(n - 1, "0")
'保存到动态数组的新元素中
d(n) =
MyResult
Next i
'>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
'调用求和函数,把得到的积累加起来
MyResult = "0"
For i = 1 To UBound(d)
MyResult =
Myaddition(MyResult, d(i))
Next i
MyProduct = MyResult
End Function
四、计算100!
最后由于比较简单,直接给出代码:
Private Sub MyFactorial(ByVal n As
Integer)
Dim i As Integer
Dim J As String
J = “1”
For i=1 To n
J =
MyProduct(J,Cstr(i))
Next i
Print J
End Sub
本文涉及到的代码,均在VB6环境中通过运行测试,若有不当或错误之处,敬请指正。
最后说明:
若需要在VB.NET环境中使用上述代码,请作如下修改处理;
1.把Left()函数改为:Microsoft.VisualBasic.Left()
2.把Right()函数改为:Microsoft.VisualBasic.Right()
3.把String()函数改为:StrDup()
2016年12月4日