String Math Module (Big Integer Library) for VBA (Visual Basic for Applications)

Dec 14, 2010 13:31

I was recently working on an Access database for asset management for an IT department. Most of the computers they work with are Dells. Now a Dell has two numbers associated with it: an alphanumeric "service tag" and a plain numeric "express service code." Before this, I was working on a program to grab this information programmatically and generate a report. The service tag was easy, since it's the same as the serial number, which can be easily retrieved with WMIC. The express service code appeared to be harder, until I found out that the express service code is really just the serial number converted from base 36 to base 10! So this database contained two fields, for the serial number and the express service code, and I wanted to add a bit of functionality to it that would convert one to the other. The problem I quickly ran into is that VBA's Long type is only 32 bits wide - not enough to hold a serial number. I tried using Currency, but that would still overflow with any sufficiently long serial number (it was fine for Dell's 7-character serial numbers, but not Apple's 11-character serial numbers, for instance). I tried using Decimal, but VBA has no native support for Decimal, and I just couldn't get it to work. There is System.BigInteger, but that's only in .NET 4.0 and later. Finally I determined that the only way I could make this work is if I wrote a big number library myself. So here it is, string math for VBA:


Attribute VB_Name = "BigIntMath" Option Compare Database Private Type PartialDivideInfo Quotient As Integer Subtrahend As String Remainder As String End Type Private LastRemainder As String Private Const Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" Public Function Compare(a As String, b As String) As Integer Dim an, bn, rn As Boolean Dim i, av, bv As Integer an = (Left(a, 1) = "-") bn = (Left(b, 1) = "-") If an Then a = Mid(a, 2) If bn Then b = Mid(b, 2) If an And bn Then rn = True ElseIf bn Then Compare = 1 Exit Function ElseIf an Then Compare = -1 Exit Function Else rn = False End If Do While Len(a) > 1 And Left(a, 1) = "0" a = Mid(a, 2) Loop Do While Len(b) > 1 And Left(b, 1) = "0" b = Mid(b, 2) Loop If Len(a) < Len(b) Then Compare = -1 ElseIf Len(a) > Len(b) Then Compare = 1 Else Compare = 0 For i = 1 To Len(a) av = CInt(Mid(a, i, 1)) bv = CInt(Mid(b, i, 1)) If av < bv Then Compare = -1 Exit For ElseIf av > bv Then Compare = 1 Exit For End If Next i End If If rn Then Compare = -Compare End If End Function Public Function Add(a As String, b As String) As String Dim an, bn, rn As Boolean Dim ai, bi, carry As Integer an = (Left(a, 1) = "-") bn = (Left(b, 1) = "-") If an Then a = Mid(a, 2) If bn Then b = Mid(b, 2) If an And bn Then rn = True ElseIf bn Then Add = Subtract(a, b) Exit Function ElseIf an Then Add = Subtract(b, a) Exit Function Else rn = False End If ai = Len(a) bi = Len(b) carry = 0 Add = "" Do While ai > 0 And bi > 0 carry = carry + CInt(Mid(a, ai, 1)) + CInt(Mid(b, bi, 1)) Add = CStr(carry Mod 10) + Add carry = carry \ 10 ai = ai - 1 bi = bi - 1 Loop Do While ai > 0 carry = carry + CInt(Mid(a, ai, 1)) Add = CStr(carry Mod 10) + Add carry = carry \ 10 ai = ai - 1 Loop Do While bi > 0 carry = carry + CInt(Mid(b, bi, 1)) Add = CStr(carry Mod 10) + Add carry = carry \ 10 bi = bi - 1 Loop Add = CStr(carry) + Add Do While Len(Add) > 1 And Left(Add, 1) = "0" Add = Mid(Add, 2) Loop If Add <> "0" And rn Then Add = "-" + Add End If End Function Private Function RealMod(a As Integer, b As Integer) As Integer If a Mod b = 0 Then RealMod = 0 ElseIf a < 0 Then RealMod = b + a Mod b Else RealMod = a Mod b End If End Function Private Function RealDiv(a As Integer, b As Integer) As Integer If a Mod b = 0 Then RealDiv = a \ b ElseIf a < 0 Then RealDiv = a \ b - 1 Else RealDiv = a \ b End If End Function Public Function Subtract(a As String, b As String) As String Dim an, bn, rn As Boolean Dim ai, bi, barrow As Integer an = (Left(a, 1) = "-") bn = (Left(b, 1) = "-") If an Then a = Mid(a, 2) If bn Then b = Mid(b, 2) If an And bn Then rn = True ElseIf bn Then Subtract = Add(a, b) Exit Function ElseIf an Then Subtract = "-" + Add(a, b) Exit Function Else rn = False End If barrow = Compare(a, b) If barrow = 0 Then Subtract = "0" Exit Function ElseIf barrow < 0 Then Subtract = a a = b b = Subtract rn = Not rn End If ai = Len(a) bi = Len(b) barrow = 0 Subtract = "" Do While ai > 0 And bi > 0 barrow = barrow + CInt(Mid(a, ai, 1)) - CInt(Mid(b, bi, 1)) Subtract = CStr(RealMod(barrow, 10)) + Subtract barrow = RealDiv(barrow, 10) ai = ai - 1 bi = bi - 1 Loop Do While ai > 0 barrow = barrow + CInt(Mid(a, ai, 1)) Subtract = CStr(RealMod(barrow, 10)) + Subtract barrow = RealDiv(barrow, 10) ai = ai - 1 Loop Do While Len(Subtract) > 1 And Left(Subtract, 1) = "0" Subtract = Mid(Subtract, 2) Loop If Subtract <> "0" And rn Then Subtract = "-" + Subtract End If End Function Public Function Multiply(a As String, b As String) As String Dim an, bn, rn As Boolean Dim m() As Long Dim al, bl, ai, bi As Integer Dim carry As Long an = (Left(a, 1) = "-") bn = (Left(b, 1) = "-") If an Then a = Mid(a, 2) If bn Then b = Mid(b, 2) rn = (an <> bn) al = Len(a) bl = Len(b) ReDim m(1 To (al + bl - 1)) For ai = 1 To al For bi = 1 To bl m(ai + bi - 1) = m(ai + bi - 1) + CLng(Mid(a, al - ai + 1, 1)) * CLng(Mid(b, bl - bi + 1, 1)) Next bi Next ai carry = 0 Multiply = "" For ai = 1 To al + bl - 1 carry = carry + m(ai) Multiply = CStr(carry Mod 10) + Multiply carry = carry \ 10 Next ai Multiply = CStr(carry) + Multiply Do While Len(Multiply) > 1 And Left(Multiply, 1) = "0" Multiply = Mid(Multiply, 2) Loop If Multiply <> "0" And rn Then Multiply = "-" + Multiply End If End Function Private Function PartialDivide(a As String, b As String) As PartialDivideInfo For PartialDivide.Quotient = 9 To 1 Step -1 PartialDivide.Subtrahend = Multiply(b, CStr(PartialDivide.Quotient)) If Compare(PartialDivide.Subtrahend, a) <= 0 Then PartialDivide.Remainder = Subtract(a, PartialDivide.Subtrahend) Exit Function End If Next PartialDivide.Quotient PartialDivide.Quotient = 0 PartialDivide.Subtrahend = "0" PartialDivide.Remainder = a End Function Public Function Divide(a As String, b As String) As String Dim an, bn, rn As Boolean Dim c As Integer Dim s As String Dim d As PartialDivideInfo an = (Left(a, 1) = "-") bn = (Left(b, 1) = "-") If an Then a = Mid(a, 2) If bn Then b = Mid(b, 2) rn = (an <> bn) If Compare(b, "0") = 0 Then Err.Raise 11 Exit Function ElseIf Compare(a, "0") = 0 Then Divide = "0" LastRemainder = "0" Exit Function End If c = Compare(a, b) If c < 0 Then Divide = "0" LastRemainder = a Exit Function ElseIf c = 0 Then If rn Then Divide = "-1" Else Divide = "1" End If LastRemainder = "0" Exit Function End If Divide = "" s = "" For c = 1 To Len(a) s = s + Mid(a, c, 1) d = PartialDivide(s, b) Divide = Divide + CStr(d.Quotient) s = d.Remainder Next c Do While Len(Divide) > 1 And Left(Divide, 1) = "0" Divide = Mid(Divide, 2) Loop If Divide <> "0" And rn Then Divide = "-" + Divide End If LastRemainder = s End Function Public Function LastModulus() As String LastModulus = LastRemainder End Function Public Function Modulus(a As String, b As String) As String Divide a, b Modulus = LastRemainder End Function Public Function BigIntFromString(s As String, base As Integer) As String Dim rn As Boolean Dim bs As String Dim i, v As Integer If Left(s, 1) = "-" Then rn = True s = Mid(s, 2) Else rn = False End If bs = CStr(base) BigIntFromString = "0" For i = 1 To Len(s) v = InStr(Alphabet, UCase(Mid(s, i, 1))) If v > 0 Then BigIntFromString = Multiply(BigIntFromString, bs) BigIntFromString = Add(BigIntFromString, CStr(v - 1)) End If Next i If rn Then BigIntFromString = "-" + BigIntFromString End If End Function Public Function BigIntToString(s As String, base As Integer) As String Dim rn As Boolean Dim bs As String Dim v As Integer If Left(s, 1) = "-" Then rn = True s = Mid(s, 2) Else rn = False End If bs = CStr(base) BigIntToString = "" Do While Compare(s, "0") > 0 s = Divide(s, bs) v = CInt(LastModulus()) BigIntToString = Mid(Alphabet, v + 1, 1) + BigIntToString Loop If BigIntToString = "" Then BigIntToString = "0" ElseIf BigIntToString <> "0" And rn Then BigIntToString = "-" + BigIntToString End If End Function

A fair bit of warning, this doesn't do very extensive error checking to make sure the string inputs are in a valid format. That can easily be added, however. And it does check for division by zero, and throw a division by zero exception if that happens.

bloggish, compsci

Previous post Next post
Up