Big number wrapper (using GMP int)

Source-code only - please, don't post questions here.
yetifoot
Posts: 1710
Joined: Sep 11, 2005 7:08
Location: England
Contact:

Big number wrapper (using GMP int)

Postby yetifoot » Jan 18, 2007 12:14

This is a small wrapper I put together for GMP big integer functions, it uses the new CVS features to make a TYPE (gmp_int) that you can use directly using normal operators.

Save the files using the names given, then compile using:

fbc test.bas gmp_int.bas


I haven't included some of the more advanced stuff like kronecker, as to be honest I don't know what it is :), however it covers most of the regular stuff like +-*/\mod, and also shows the usage of the new feature for FOR loops using a TYPE.

You will need GMP for it to work, on linux you can either build yourself, or get a package the usual way, for windows I found this page http://cs.nyu.edu/exact/core/gmp/ that seems to offer the DLL compiled, its probably best to get the dynamic for mingw version (I don't use windows so i can't test that)

test.bas

Code: Select all

#include "gmp_int.bi"

' Powers of two
print "Some powers of two"
for i as integer = 0 to 128
   print "2 ^ " & i & " = " & gmp_int(2) ^ gmp_int(i)
next i

' Big numbers in FOR
print "Big numbers in FOR loop"
for i as gmp_int = gmp_int("10000000000000000") to gmp_int("50000000000000000") step gmp_int("20000000000000000")
   print i
next i

' Printing a big number using a non 10 number base.

dim n as gmp_int = gmp_int("123456789123456789123456789123456789")
print "Printing a big number in base 16"
print n, n.getString( 16 )


gmp_int.bas

Code: Select all

#include "gmp_int.bi"

' Any time we allocate with sizeof( __mpz_struct ), we add the safety net onto the size,
' without this it crashes for me, perhaps the __mpz_struct is declared wrong in gmp.bi,
' whatever the reason it doesn't matter too much, as the struct is only manipulated by
' the gmp functions.
const SAFETY_NET = 20

'::::::::
constructor gmp_int ( )

   num = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
   mpz_init_set_si( num, 0 )
   
end constructor

'::::::::
constructor gmp_int ( byval i as integer )

   num = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
   mpz_init_set_si( num, i )
   
end constructor

'::::::::
constructor gmp_int ( byref s as string )

   num = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
   mpz_init_set_str( num, strptr( s ), 10 )
   
end constructor

'::::::::
constructor gmp_int ( byref g as gmp_int )

   num = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
   mpz_init_set( num, g.num )
   
end constructor

'::::::::
destructor gmp_int ( )

   mpz_clear( num )
   deallocate( num )

end destructor

'::::::::
operator gmp_int.let ( byref g as gmp_int )

   mpz_set( num, g.num )
   
end operator

'::::::::
operator gmp_int.let ( byval i as integer )

   mpz_set_si( num, i )
   
end operator

'::::::::
operator gmp_int.let ( byref s as string )

   mpz_set_str( num, strptr(s), 10 )
   
end operator

'::::::::
operator gmp_int.cast ( ) as string

   operator = getString( 10 )

end operator

'::::::::
operator gmp_int.for ( byref stp as gmp_int )

   '' note: if STEP isn't explicitly given, it will be NULL
   if @stp = 0 then
      is_up = -1
   else
      is_up = (stp > 0)
   end if
   
end operator

'::::::::
operator gmp_int.step ( byref stp as gmp_int )
   
   this += stp
   
end operator

'::::::::
operator gmp_int.next ( byref end_cond as gmp_int ) as integer
   
   if is_up then
      operator = this <= end_cond
   else
      operator = this >= end_cond
   end if
   
end operator

'::::::::
function gmp_int.getString ( byval _base as integer = 10 ) as string

   dim as zstring ptr s = mpz_get_str( 0, _base, num )

      if s then
         function = *s
         deallocate( s )
      end if

end function

'::::::::
operator + ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_add( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator - ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_sub( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator * ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_mul( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator \ ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_tdiv_q( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator / ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   ' This really is quite nasty, but it's the best way I could come up with
   ' to do an FP divide, while rounding results the way people may expect them.
   ' 0.5 becomes 1, 0.4 becomes 0, -0.5 becomes -1, -0.4 becomes 0
   ' I'm not 100% that temp is needed, but I used it just to be safe, for some
   ' GMP functions it seems a bad idea to pass an mpz as dest and one of the sources.
   
   dim as mpf_ptr l, r, q, half, temp
   dim as integer cmp_val
   dim as gmp_int result
   
      l = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
      r = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
      q = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
      half = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
      temp = callocate( sizeof( __mpz_struct ) + SAFETY_NET )
      
      mpf_init( l )
      mpf_init( r )
      mpf_init( q )
      mpf_init_set_str( half, "0.50", 10 )
      mpf_init( temp )
      
      mpf_set_z( l, lhs.num )
      mpf_set_z( r, rhs.num )
      mpf_div( q, l, r )
      
      cmp_val = mpf_cmp_d( q, 0 )
   
      if cmp_val > 0 then
         mpf_add( temp, q, half )
      elseif cmp_val < 0 then
         mpf_sub( temp, q, half )
      else
         mpf_set( temp, q )
      end if
      
      mpf_trunc( q, temp )
      
      mpz_set_f( result.num, q )
   
      mpf_clear( l )
      mpf_clear( r )
      mpf_clear( q )
      mpf_clear( half )
      mpf_clear( temp )
      
      deallocate( l )
      deallocate( r )
      deallocate( q )
      deallocate( half )
      deallocate( temp )
      
      operator = result
   
end operator

'::::::::
operator ^ ( byref lhs as gmp_int, byval rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_pow_ui( result.num, lhs.num, mpz_get_ui( rhs.num ) )
      
      operator = result

end operator

'::::::::
operator mod ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_mod( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator and ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_and( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator or ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_ior( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator xor ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_xor( result.num, lhs.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator - ( byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_neg( result.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator not ( byref rhs as gmp_int ) as gmp_int

   dim as gmp_int result

      mpz_com( result.num, rhs.num )
      
      operator = result

end operator

'::::::::
operator = ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) = 0)

end operator

'::::::::
operator < ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) < 0)

end operator

'::::::::
operator > ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) > 0)

end operator

'::::::::
operator <= ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) <= 0)

end operator

'::::::::
operator >= ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) >= 0)

end operator

'::::::::
operator <> ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

   operator = (mpz_cmp( lhs.num, rhs.num ) <> 0)

end operator


gmp_int.bi

Code: Select all

#ifndef __GMP_INT_BI__
#define __GMP_INT_BI__

#include "gmp.bi"

type gmp_int
   declare constructor ( )
   declare constructor ( byval i as integer )
   declare constructor ( byref s as string )
   declare constructor ( byref g as gmp_int )
   declare destructor ( )
   declare operator let ( byref g as gmp_int )
   declare operator let ( byval i as integer )
   declare operator let ( byref s as string )
   declare operator cast ( ) as string
   declare operator for ( byref stp as gmp_int )
   declare operator step ( byref stp as gmp_int )
   declare operator next ( byref end_cond as gmp_int ) as integer
   declare function getString ( byval _base as integer = 10 ) as string
   num as mpz_ptr = 0
   is_up as integer
end type

declare operator + ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator - ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator * ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator \ ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator / ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator ^ ( byref lhs as gmp_int, byval rhs as gmp_int ) as gmp_int
declare operator mod ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator and ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator or ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator xor ( byref lhs as gmp_int, byref rhs as gmp_int ) as gmp_int
declare operator - ( byref rhs as gmp_int ) as gmp_int
declare operator not ( byref rhs as gmp_int ) as gmp_int
declare operator = ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer
declare operator < ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer
declare operator > ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer
declare operator <= ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer
declare operator >= ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer
declare operator <> ( byref lhs as gmp_int, byref rhs as gmp_int ) as integer

#endif '__GMP_INT_BI__
albert
Posts: 4041
Joined: Sep 28, 2006 2:41
Location: California, USA

Re: Big number wrapper (using GMP int)

Postby albert » May 11, 2013 16:45

@YetiFoot

Your FOR , NEXT code wouldn't run , I got it working with Richards help.. It was locking up at the NEXT..
I think it was the is_up var that made it fail ???

Code: Select all

#ifndef __GMP_INT_BI__
#define __GMP_INT_BI__

#include "gmp.bi"

Type gmp_int
    Declare Constructor ( )
    Declare Constructor ( Byval i As Integer )
    Declare Constructor ( Byref s As String )
    Declare Constructor ( Byref g As gmp_int )
    Declare Destructor ( )
    Declare Operator Let ( Byref g As gmp_int )
    Declare Operator Let ( Byval i As Integer )
    Declare Operator Let ( Byref s As String )
    Declare Operator Cast ( ) As String
    '----------------------------------------------
    ' For Next Implicit step = +1
    Declare Operator For ( )
    Declare Operator Step( )
    Declare Operator Next( Byref end_cond As gmp_int ) As Integer
    ' For Next Exlicit step
    Declare Operator For ( Byref stp As gmp_int )
    Declare Operator Step( Byref stp As gmp_int )
    Declare Operator Next( Byref end_cond As gmp_int, Byref step_var As gmp_int ) As Integer
    '----------------------------------------------
    Declare Function getString ( Byval _base As Integer = 10 ) As String
    num As mpz_ptr = 0
    is_up As Integer
End Type

Declare Operator + ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator - ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator * ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator \ ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator / ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator ^ ( Byref lhs As gmp_int, Byval rhs As gmp_int ) As gmp_int
Declare Operator Mod ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator And ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator Or ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator xor ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Declare Operator - ( Byref rhs As gmp_int ) As gmp_int
Declare Operator Not ( Byref rhs As gmp_int ) As gmp_int
Declare Operator = ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Declare Operator < ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Declare Operator > ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Declare Operator <= ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Declare Operator >= ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Declare Operator <> ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer

#endif '__GMP_INT_BI__


' Any time we allocate with sizeof( __mpz_struct ), we add the safety net onto the size,
' without this it crashes for me, perhaps the __mpz_struct is declared wrong in gmp.bi,
' whatever the reason it doesn't matter too much, as the struct is only manipulated by
' the gmp functions.
Const SAFETY_NET = 20

'::::::::
Constructor gmp_int ( )
num = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
mpz_init_set_si( num, 0 )
End Constructor

'::::::::
Constructor gmp_int ( Byval i As Integer )
num = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
mpz_init_set_si( num, i )
End Constructor

'::::::::
Constructor gmp_int ( Byref s As String )
num = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
mpz_init_set_str( num, Strptr( s ), 10 )
End Constructor

'::::::::
Constructor gmp_int ( Byref g As gmp_int )
num = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
mpz_init_set( num, g.num )
End Constructor

'::::::::
Destructor gmp_int ( )
mpz_clear( num )
Deallocate( num )
End Destructor

'::::::::
Operator gmp_int.let ( Byref g As gmp_int )
mpz_set( num, g.num )
End Operator

'::::::::
Operator gmp_int.let ( Byval i As Integer )
mpz_set_si( num, i )
End Operator

'::::::::
Operator gmp_int.let ( Byref s As String )
mpz_set_str( num, Strptr(s), 10 )
End Operator

'::::::::
Operator gmp_int.cast ( ) As String
Operator = getString( 10 )
End Operator

'=====================================================================
' Implicit step   gmp_int.For   gmp_int.Next   gmp_int.Step  is +1

'::::::::
Operator gmp_int.For ( )
End Operator

'::::::::
Operator gmp_int.Next( Byref end_cond As gmp_int ) As Integer
Return This <= end_cond
End Operator

'::::::::
Operator gmp_int.Step( )
This += gmp_int(1)
End Operator

'=====================================================================
' Explicit step   gmp_int.For   gmp_int.Next   gmp_int.Step

'::::::::
Operator gmp_int.For ( Byref stp As gmp_int )
If @stp = 0 Then ' implicit step if NULL
    is_up = -1
Else
    is_up = (stp > 0)
End If
End Operator

'::::::::
Operator gmp_int.Next( Byref end_cond As gmp_int, Byref step_var As gmp_int ) As Integer
If step_var < gmp_int Then
    Return This >= end_cond
Else
    Return This <= end_cond
End If
End Operator

'::::::::
Operator gmp_int.Step( Byref stp As gmp_int )
This += stp
End Operator

'=====================================================================

'::::::::
Function gmp_int.getString ( Byval _base As Integer = 10 ) As String
    Dim As zstring Ptr s = mpz_get_str( 0, _base, num )
    If s Then
        Function = *s
        Deallocate( s )
    End If
End Function

'::::::::
Operator + ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_add( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator - ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_sub( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator * ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_mul( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator \ ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_tdiv_q( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator / ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int

' This really is quite nasty, but it's the best way I could come up with
' to do an FP divide, while rounding results the way people may expect them.
' 0.5 becomes 1, 0.4 becomes 0, -0.5 becomes -1, -0.4 becomes 0
' I'm not 100% that temp is needed, but I used it just to be safe, for some
' GMP functions it seems a bad idea to pass an mpz as dest and one of the sources.

Dim As mpf_ptr l, r, q, half, temp
Dim As Integer cmp_val
Dim As gmp_int result

l = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
r = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
q = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
half = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )
temp = Callocate( Sizeof( __mpz_struct ) + SAFETY_NET )

mpf_init( l )
mpf_init( r )
mpf_init( q )
mpf_init_set_str( half, "0.50", 10 )
mpf_init( temp )

mpf_set_z( l, lhs.num )
mpf_set_z( r, rhs.num )
mpf_div( q, l, r )

cmp_val = mpf_cmp_d( q, 0 )

If cmp_val > 0 Then
    mpf_add( temp, q, half )
Elseif cmp_val < 0 Then
    mpf_sub( temp, q, half )
Else
    mpf_set( temp, q )
End If

mpf_trunc( q, temp )

mpz_set_f( result.num, q )

mpf_clear( l )
mpf_clear( r )
mpf_clear( q )
mpf_clear( half )
mpf_clear( temp )

Deallocate( l )
Deallocate( r )
Deallocate( q )
Deallocate( half )
Deallocate( temp )

Operator = result

End Operator

'::::::::
Operator ^ ( Byref lhs As gmp_int, Byval rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_pow_ui( result.num, lhs.num, mpz_get_ui( rhs.num ) )
Operator = result
End Operator

'::::::::
Operator Mod ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_mod( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator And ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_and( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator Or ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_ior( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator xor ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_xor( result.num, lhs.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator - ( Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_neg( result.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator Not ( Byref rhs As gmp_int ) As gmp_int
Dim As gmp_int result
mpz_com( result.num, rhs.num )
Operator = result
End Operator

'::::::::
Operator = ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) = 0)
End Operator

'::::::::
Operator < ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) < 0)
End Operator

'::::::::
Operator > ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) > 0)
End Operator

'::::::::
Operator <= ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) <= 0)
End Operator

'::::::::
Operator >= ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) >= 0)
End Operator

'::::::::
Operator <> ( Byref lhs As gmp_int, Byref rhs As gmp_int ) As Integer
Operator = (mpz_cmp( lhs.num, rhs.num ) <> 0)
End Operator



'===============================================================================
'===============================================================================
'===============================================================================
'test code
'===============================================================================
'===============================================================================
'===============================================================================
'#include "gmp_int.bi"

' Powers of two
Print "Some powers of two"
For i As integer = 0 to 128
    Print "2 ^ " & i & " = " & gmp_int(2) ^ gmp_int(i)
Next i

' Big numbers in FOR
'print "Big numbers in FOR loop"
Print
Print "For , Next Going positive"
For i As gmp_int = gmp_int("10000000000000000") To gmp_int("50000000000000000") Step gmp_int("10000000000000000")
    Print i
Next i

'===============================================================================
Print
Print "For , Next    Implicit , Going positive"
For i As gmp_int = gmp_int("10000000000000000") To gmp_int("10000000000000009")
    Print i
Next i

'===============================================================================
Print
Print "For , Next Going negative"
For i As gmp_int = gmp_int("-10000000000000000") To gmp_int("-50000000000000000") Step gmp_int("-10000000000000000")
    Print i
Next i

' Printing a big number using a non 10 number base.

Dim n As gmp_int = gmp_int("123456789123456789123456789123456789")
Print "Printing a big number in base 16"
Print n, n.getString( 16 )


Sleep
End



Now it goes positive or negative ..

Updated 5-12-2013 (Richard corrected the previous posted code to allow both implicit and explicit ( STEP )

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 1 guest