Showing posts with label bignum. Show all posts
Showing posts with label bignum. Show all posts

Saturday, September 26, 2015

[mLite] Arbitrary Precision Integers and Logarithms

Yeah yeah, there's something 'bout you baby I like

-- Status Quo, 1981.

It's a bit worrisome when I quote musicians from last century, but I do like mLite. Granted, I don't dream about it, but it's fun to figure out how to do things in a functional way.

In this case I was wanting to solve the RosettaCode challenge
Arbitrary-precision integers (included) which has one calculate 5^4^3^2, find out how many digits there are and then display the top 20 digits and bottom 20 digits. Bignums weren't a challenge, as they are supported natively in mLite. No, the biggest challenge was figuring out how many digits. 5^4^3^2 is an extremely large number, with 183231 digits and all of my first attempts did not complete even after running for a day.

mLite's creator,
Nils M. Holm, suggested I use logarithms. However, logarithms aren't built in to mLite yet, so I had to cook up my own logarithm library. So how do you calculate logarithms by hand? I dug around a bit and found an article on Quora entitled How can we calculate the logarithms by hand without using any calculator?. Harald Overbeek's description formed the basis for the mLite code below.
fun ntol (0, x) = if len x < 1 then [0] else x
       | (n, x) = ntol (n div 10, (n mod 10) :: x)
       | n      = ntol (n, [])
;
ntol converts a number to a list.
fun powers_of_10 9 = 1000000000
               | 8 = 100000000
               | 7 = 10000000
               | 6 = 1000000
               | 5 = 100000
               | 4 = 10000
               | 3 = 1000
               | 2 = 100
               | 1 = 10
               | 0 = 1
;
powers_of_10 is a precomputation of all the powers I knew I'd encounter during the calculation. This alone sped up the code a lot.
fun size (c, 0) = c
       | (c, n > 9999999999) = size (c + 10, trunc (n / 10000000000))
       | (c, n)              = size (c +  1, trunc (n / 10))
       | n                   = size (     0, trunc (n / 10))
;
size works out the number of digits by keeping track of how many calculations it takes to divide the number by 10 until the remainder is zero.
fun makeVisible L = map (fn x = if int x then chr (x + 48) else x) L
makeVisible turns an array of digits into a string, with handling for non-numeric elements
fun log10 (n, 0, x) = ston  implode  makeVisible ` rev x
        | (n, decimals, x) =
            let val n' = n^10;
              val size_n' = size n'
            in 
              log10 (n' / powers_of_10 size_n', decimals - 1, size_n' :: x)
   end
        | (n, decimals) =
            let
              val size_n = size n
            in
              log10 (n / 10^size_n, decimals, #"." :: rev (ntol size_n) @ [])
            end
;
Then log10 ties it all together. The second parameter specifies the number of digits precision. Below I've specified 6.

Being somewhat mathematically challenged, I still had to figure out how to use the library I had made to calculate the digits. Thankfully, there's a
mathematics community on StackExchange. They helped me out a lot. Now I could work out the number of digits in the number by rounding up the result of log10(5) * 4^9 (seeing as 3^2 is 9).

Thus I ended up with
val fourThreeTwo = 4^3^2;
val fiveFourThreeTwo = 5^fourThreeTwo;

val digitCount = trunc (log10(5,6) * fourThreeTwo + 0.5);
print "Count  = "; println digitCount;

val end20 = fiveFourThreeTwo mod (10^20);
print "End 20 = "; println end20;

val top20 = fiveFourThreeTwo div (10^(digitCount - 20)); 
print "Top 20 = "; println top20;
Which, after 1 hour and 9 minutes on an AMD A6 cpu, gave
>mlite -f 5p4p3p2.m
 Count = 183231
 End 20 = 92256259918212890625
 Top 20 = 62060698786608744707 
A nice change from my day job which centres around JavaScript, Peloton, HTML, PHP and the odd bit of T-SQL. Enjoy!

© Copyright Bruce M. Axtens, 2015

Tuesday, February 25, 2014

[VBScript] How can I display 64 bit double number using VBScript on 32 Bit OS?

"How can I display 64 bit double number using VBScript on 32 Bit OS?" That was the question on StackOverflow back at the end of 2012. And nobody had answered it. What a challenge!

I assumed a pure-VBScript solution was needed, and set about finding and implementing a Very Large Integer class with which I could then implement a Hex64 to integer function. A workable candidate function was found on Rosetta Code in the
Liberty BASIC solution to the Long Multiplication task.

The code for the class is as follows. It's pretty much the same as the original.

Option Explicit
Class VeryLongInteger
  'http://rosettacode.org/wiki/Long_Multiplication#Liberty_BASIC
  Public Function MULTIPLY(Str_A, Str_B)
    Dim signA, signB, sResult, Str_Shift, i, d, Str_T
    signA = 1
    If Left(Str_A,1) = "-" Then 
      Str_A = Mid(Str_A,2)
      signA = -1
    End If
    signB = 1
    If Left(Str_B,1) = "-" Then 
      Str_B = Mid(Str_B,2)
      signB = -1
    End If
    sResult = vbNullString
    Str_T = vbNullString
    Str_shift = vbNullString
    For i = Len(Str_A) To 1 Step -1
      d = CInt(Mid(Str_A,i,1))
      Str_T = MULTBYDIGIT(Str_B, d)
      sResult = ADD(sResult, Str_T & Str_shift)
      Str_shift = Str_shift & "0"
      'print d, Str_T, sResult 
    Next
    If signA * signB < 0 Then sResult = "-" + sResult
    'print sResult
    MULTIPLY = sResult
  End Function
  
  Private Function MULTBYDIGIT(Str_A, d)
    Dim sResult, carry, i, a, c
    'multiply Str_A by digit d
    sResult = vbNullString
    carry = 0
    For i = Len(Str_A) To 1 Step -1
      a = CInt(Mid(Str_A,i,1))
      c = a * d + carry
      carry = c \ 10
      c = c Mod 10
      'print a, c
      sResult = CStr(c) & sResult 
    Next
    If carry > 0 Then sResult = CStr(carry) & sResult
    'print sResult
    MULTBYDIGIT = sResult
  End Function
  
  Public Function ADD(Str_A, Str_B)
    Dim L, sResult, carry, i, a, b, c
    'add Str_A + Str_B, for now only positive
    l = MAX(Len(Str_A), Len(Str_B))
    Str_A=PAD(Str_A,l)
    Str_B=PAD(Str_B,l)
    sResult = vbNullString 'result
    carry = 0
    For i = l To 1 Step -1
      a = CInt(Mid(Str_A,i,1))
      b = CInt(Mid(Str_B,i,1))
      c = a + b + carry
      carry = Int(c/10)
      c = c Mod 10
      'print a, b, c
      sResult = CStr(c) & sResult
    Next
    If carry>0 Then sResult = CStr(carry) & sResult
    'print sResult
    ADD = sResult
  End Function
  
  Private Function Max(a,b)
    If a > b Then
      Max = a
    Else
      Max = b
    End If
  End Function
  
  Private Function pad(a,n)  'pad from right with 0 to length n
    Dim sResult
    sResult = a
    While Len(sResult) < n
      sResult = "0" & sResult
    Wend
    pad = sResult
  End Function
End Class
With that defined I have now all I need to implement a Hex64 function. This I did in two forms:

* A memoized version which precomputes all the relevant powers of 16

Function PrecomputedFromHex64(sHex)
  Dim VLI
  Set VLI = New VeryLongInteger
  
  Dim Sixteen(16)
  Sixteen(0) = "1"
  Sixteen(1) = "16"
  Sixteen(2) = VLI.MULTIPLY(Sixteen(1),"16")
  Sixteen(3) = VLI.MULTIPLY(Sixteen(2),"16")
  Sixteen(4) = VLI.MULTIPLY(Sixteen(3),"16")
  Sixteen(5) = VLI.MULTIPLY(Sixteen(4),"16")
  Sixteen(6) = VLI.MULTIPLY(Sixteen(5),"16")
  Sixteen(7) = VLI.MULTIPLY(Sixteen(6),"16")
  Sixteen(8) = VLI.MULTIPLY(Sixteen(7),"16")
  Sixteen(9) = VLI.MULTIPLY(Sixteen(8),"16")
  Sixteen(10) = VLI.MULTIPLY(Sixteen(9),"16")
  Sixteen(11) = VLI.MULTIPLY(Sixteen(10),"16")
  Sixteen(12) = VLI.MULTIPLY(Sixteen(11),"16")
  Sixteen(13) = VLI.MULTIPLY(Sixteen(12),"16")
  Sixteen(14) = VLI.MULTIPLY(Sixteen(13),"16")
  Sixteen(15) = VLI.MULTIPLY(Sixteen(14),"16")
  
  Dim theAnswer, i, theDigit, theMultiplier, thePower, aPower
  theAnswer = "0"
  aPower = 0
  For i = Len(sHex) To 1 Step -1
    theDigit = UCase(Mid(sHex,i,1))
    theMultiplier = InStr("0123456789ABCDEF",theDigit)-1
    thePower = Sixteen(aPower)
    thePower = VLI.MULTIPLY(CStr(theMultiplier),thePower)
    theAnswer = VLI.ADD(theAnswer,thePower )
    aPower = aPower + 1
  Next
  PrecomputedFromHex64 = theAnswer
End Function
* A non-memoized version which computes the relevant power when it is needed.
Function FromHex64(sHex)
  Dim VLI
  Set VLI = New VeryLongInteger       
  Dim theAnswer, i, theDigit, theMultiplier, thePower, aPower
  theAnswer = "0"
  thePower = "1"
  For i = Len(sHex) To 1 Step -1
    theDigit = UCase(Mid(sHex,i,1))
    theMultiplier = InStr("0123456789ABCDEF",theDigit)-1
    theAnswer = VLI.ADD(theAnswer,VLI.MULTIPLY(thePower,theMultiplier))
    thePower = VLI.MULTIPLY(thePower,"16")
  Next
  FromHex64 = theAnswer
End Function
The memoized version is slightly faster than the non-memoized, despite the overhead of the memoization. If the class/function pair were to be used a lot in a script, one might consider making both the VLI instantiation and the Sixteen() array global and precomputing the array at the beginning of the script rather than on each invocation.

A rough test of the code follows:

Const test = "FFFFFFFFFFFFFFFF" '"41417724EBA8953E"
Dim t, I, S
t=Timer
For I = 1 To 100
  S = FromHex64(test)
Next
WScript.Echo "No memoization", Timer-t

t=Timer
For I = 1 To 100
  S = PrecomputedFromHex64(test)
Next
WScript.Echo "Memoized each time",Timer-t

Function GlobalMemoFromHex64(sHex)  
  Dim theAnswer, i, theDigit, theMultiplier, thePower, aPower
  theAnswer = "0"
  aPower = 0
  For i = Len(sHex) To 1 Step -1
    theDigit = UCase(Mid(sHex,i,1))
    theMultiplier = InStr("0123456789ABCDEF",theDigit)-1
    thePower = Sixteen(aPower)
    thePower = VLI.MULTIPLY(CStr(theMultiplier),thePower)
    theAnswer = VLI.ADD(theAnswer,thePower )
    aPower = aPower + 1
  Next
  GlobalMemoFromHex64 = theAnswer
End Function

Dim VLI
Set VLI = New VeryLongInteger

Dim Sixteen(16)
Sixteen(0) = "1"
Sixteen(1) = "16"
Sixteen(2) = VLI.MULTIPLY(Sixteen(1),"16")
Sixteen(3) = VLI.MULTIPLY(Sixteen(2),"16")
Sixteen(4) = VLI.MULTIPLY(Sixteen(3),"16")
Sixteen(5) = VLI.MULTIPLY(Sixteen(4),"16")
Sixteen(6) = VLI.MULTIPLY(Sixteen(5),"16")
Sixteen(7) = VLI.MULTIPLY(Sixteen(6),"16")
Sixteen(8) = VLI.MULTIPLY(Sixteen(7),"16")
Sixteen(9) = VLI.MULTIPLY(Sixteen(8),"16")
Sixteen(10) = VLI.MULTIPLY(Sixteen(9),"16")
Sixteen(11) = VLI.MULTIPLY(Sixteen(10),"16")
Sixteen(12) = VLI.MULTIPLY(Sixteen(11),"16")
Sixteen(13) = VLI.MULTIPLY(Sixteen(12),"16")
Sixteen(14) = VLI.MULTIPLY(Sixteen(13),"16")
Sixteen(15) = VLI.MULTIPLY(Sixteen(14),"16")

t=Timer
For I = 1 To 100
  S = GlobalMemoFromHex64(test)
Next
WScript.Echo "Global memo",Timer-t
Running the above in VBSEdit a few times under varying conditions gave the following results:
No memoization 2.632813
Memoized each time 1.023438
Global memo 0.328125

No memoization 1.667969
Memoized each time 0.8867188
Global memo 0.2695313

No memoization 1.488281
Memoized each time 0.9335938
Global memo 0.3046875
The global precomputation of the memo array is fastest technique, but if you're only using the function once you'll get by with the non-memo version.

Further optimisations are possible. Compilation, of course, would make it even faster.

Enjoy!
© Copyright Bruce M. Axtens, 2014

Wednesday, September 11, 2013

[Open Source] Github and Bitbucket

The blog's been very quiet lately. Too quiet. But I've been busy.

I got started on
Github, but the only thing I have there is a failed attempt at building a JSON library for the Euphoria programming language.

Most of the more recent stuff is happening on Bitbucket where I have various implementations (javascript v1 and javascript v2 (minimal), euphoria, vb6, php, and c#) of Steve Skiena's integer bignums library and a few other bits and bobs, namely:


All projects are in varying stages of completeness, and looking for users and improvers.


© Copyright Bruce M. Axtens, 2013