$IFNDEF _MATHINC
$DEFINE _MATHINC

' You'll notice ALL the functions make use of a Double.
'  That's because you'll now be able to work with larger numbers.
' If you have an different type (such as long, or single) convert
'  it to a double, then back to it's original type if need be.

' Trunicates a Double (dNumber) to the number of decimal places (iDecimals)
'  and/or
' Pads the fractional portion of the Double with zeros (0),
'  if it's less than the number of decimal places desired (for formatting purposes)
' If the Double is a positive number... a space is added to the left side
'  (also for formatting purposes, so that both negative and positive number align)
Function Dbl2Str(ByVal dNumber As Double, ByVal iDecimals As Integer, ByVal bPad As Byte) As String
    DefInt iSign    = Sgn(dNumber)
    DefStr sDouble  = StrF$(dNumber, ffFixed, 15, 18)
    DefStr sWhole   = Left$(sDouble, Instr(sDouble,".") - 1)
    DefStr sDecimal = Mid$(sDouble, Instr(sDouble,"."))

    Do
      If Right$(sDecimal, 1) = "0" Then sDecimal = Left$(sDecimal, Len(sDecimal) - 1)
    Loop Until Right$(sDecimal, 1) <> "0"

    If (Right$(sDecimal, 1) = ".") Then sDecimal = sDecimal + "0"

    If (Len(sDecimal) > (iDecimals + 1)) Then sDecimal = Left$(sDecimal, (iDecimals + 1))

    If bPad = True Then
      If (Len(sDecimal) < iDecimals) Then sDecimal = sDecimal + String$((iDecimals - Len(sDecimal)), "0")
    End If

    Result = IIf((iSign = (-1)), sWhole, " " + sWhole) + sDecimal

    sDecimal = "" : sWhole = "" : sDouble = "" : iSign = 0
End Function

' 1) RapidQ's Ceil, Round, and Floor can not perform their function to a specific decimal place
'    (the return for each of these functions is always an Integer)
' 2) RapidQ's Round apparently rounds negative numbers in the wrong direction
'     and Ceil and Floor are not always right
'     (run the commented out "Sub rqRoundProblem" at the bottom to see proof)
' 3) RapidQ's Ceil, Round, and Floor can not perform their function on LARGE numbers
'    (the worst part about that is... if you've ever tried it... it locks your program up)
' This single function solves all 3 of those problems
'   positive numbers: floor moves towards 0, round moves away from 0 (if applicable), ceil moves away from 0
'   negative numbers: floor moves away from 0, round moves away from 0 (if applicable), ceil moves towards 0
'     Supposedly a Double can hold: 9,007,199,254,740,992 (according to the internet)
'        2147483646.9 is RapidQ's Round max
'        2147483647.9 is RapidQ's Floor max
'        2147483646.9 is RapidQ's Ceil  max
'       -2147483648.9 is RapidQ's Round min
'       -2147483647.9 is RapidQ's Floor min
'       -2147483648.9 is RapidQ's Ceil  min
' Input parameters:
'   dNumber = a valid Double type number
'     (i.e. #####.##### or #.###, or ###,#########, or ############.######, etc., etc.)
'      Note: You "might" get an unexpected result if you pass a value like...
'            1234.54321 and pass 9 for the iDigits parameter.
'   iDigits = the point (in the fractional portion of the Double)
'             where you want the ceiling or rounding or flooring to occur
'   bType = 0 for "Floor", or 1 for "Round", or 2 for "Ceil"
' Return Value:
'   It's simplier to show you some examples, rather than trying to explain
'   ex.#1) dNumber = 12345.654321, iDigits = 0, bType = 0 : returns 12345.00000 'Floor
'   ex.#2) dNumber = 12345.654321, iDigits = 2, bType = 1 : returns 12345.65000 'Round
'   ex.#3) dNumber = 12345.654321, iDigits = 1, bType = 1 : returns 12345.70000 'Round
'   ex.#4) dNumber = 12345.654321, iDigits = 0, bType = 1 : returns 12346.00000 'Round
'   ex.#5) dNumber = 12345.654321, iDigits = 0, bType = 2 : returns 12346.00000 'Ceil
'CRF is an acronym for: Ceil,Round,Floor
Function CRF(ByVal dNumber As Double, ByVal iDigits As Integer, ByVal bType As Byte) As Double
    DefByte bFloor = 0, bRound = 1, bCeil  = 2
    DefInt ipow = 0
    DefDbl ddbl = 0.0, dret = 0.0

    ipow = (10 ^ iDigits)
    ddbl = (dNumber * ipow)

    Select Case bType
      Case bFloor
        dret = IIF((Sgn(dNumber) = (-1)), ((Int(ddbl) - 1) / ipow), (Int(ddbl) / ipow))
      Case bRound
        dret = IIF((Sgn(dNumber) = (-1)), (Int(ddbl - 0.5) / ipow), (Int(ddbl + 0.5) / ipow))
      Case bCeil
        dret = IIf((Sgn(dNumber) = (-1)), (Int(ddbl) / ipow), ((Int(ddbl) + 1) / ipow))
    End Select

    Result = dret

    dret = 0.0 : ddbl = 0.0 : ipow = 0
    bCeil  = 0 : bRound = 0 : bFloor = 0
End Function

' A function that returns the number of digits
'  on the "left side" of the decimal point
'  Note: there is no way of determining the number of digits
'        on the "right side" of the decimal point
' Example...
'  DefInt iDigits = 0
'  DefDbl dDouble = 8765432187654321.0
'  iDigits = GetDigitCount(dDouble) 'returns 16
'  RichEdit.AddStrings(Str$(iDigits))
Function GetDigitCount(ByVal dNumber As Double) As Integer
    DefInt i = 1 + Floor(Log(Abs(dNumber)) / Log(10))
    Result = i
    i = 0
End Function

' This function gets the digits ranging from...
' Least Siginificant Digit (right-most digit) to Most Siginificant Digit (left-most digit)
'
' parameter (x) should be between 1 and the number of digits you need returned
'  starting at the LSD and going towards the MSD
'
' even though parameter (dDouble) is a double...
'  this function only operates on the "whole number" portion of it
'  NOTE: remove the fractional portion BEFORE calling this function!
'  footnote: the reason I made it a double is... it allows for numbers larger then an integer
'            to use an integer... 1st convert it to a double then change it back (if need be)
'
' parameter (dDouble) can range from...
'  1 up to (AND NO MORE THAN) a 16 digit number
'
' if (x) is more than the number of digits in (dDouble)...
'  then (dDouble) is returned (without the fractional portion)
'
' Example...
'  DefInt iDigits = 9
'  DefDbl dDouble = 2345678987654321.0
'  DefDbl dReturn = GetLSDtoMSD(dDouble, iDigits) 'returns 987654321
'  RichEdit.AddStrings(StrF$(dReturn,2,17,17))
Function GetLSDtoMSD(ByVal dNumber As Double, ByVal x As Integer) As Double
    DefInt Int1 = 0, Int2 = 0, Int3 = 0, i = 0
    DefDbl Dbl0 = 0, Dbl1 = 0, Dbl2 = 0

    Int1 = 1 + Floor(Log(Abs(dNumber)) / Log(10))
    Int2 = Int1 - 1
    Int3 = Int1 - x
    Dbl1 = Int(dNumber)
    For i = 1 To Int3
      Dbl2 = (Dbl1 / (10 ^ Int2))
      Dbl1 = ((Dbl2 - Int(Dbl2)) * (10 ^ Int2))
      Int2 = (Int2 - 1)
    Next i
    Dbl0 = Int(Dbl1)
    Dbl1 = 0 : Dbl1 = Dbl0

    Result = Dbl1

    Dbl2 = 0 : Dbl1 = 0 : Dbl0 = 0
    i = 0 : Int3 = 0 : Int2 = 0 : Int1 = 0
End Function

' This function returns the digit at the specified place in a number
' Example...
'  DefInt iDigit  = 12
'  DefDbl dDouble = 2345678987654321.0
'  DefInt iReturn = GetDigitAt(dDouble, iDigit) 'returns 6
'  RichEdit.AddStrings(Str$(iReturn))
Function GetDigitAt(ByVal dNumber As Double, ByVal x As Integer) As Integer
    DefDbl d = (dNumber / Exp((x - 1) * Log(10)))
    DefInt i = (((d / 10) - Int((d / 10))) * 10)
    Result = i
    i = 0
    d = 0
End Function

' A common math function found on the internet
Function Mod10(ByVal dNumber As Double) As Integer
    DefInt i = (((dNumber / 10) - Int((dNumber / 10))) * 10)
    Result = i
    i = 0
End Function

' A common math function found on the internet
Function Log10(ByVal dNumber As Double) As Double
    DefDbl d = Log(Abs(dNumber)) / Log(10)
    Result = d
    d = 0
End Function

' A common math function found on the internet
Function Exp10(ByVal dNumber As Integer) As Double
    DefDbl d = Exp(dNumber * Log(10))
    Result = d
    d = 0
End Function

$ENDIF

' the following is intentionally commented out
' it's just for your own testing purposes
' (you can delete it without harming anything above)
' it shows you RapidQ's Round function
' rounds negative numbers in the worng direction
' and Ceil and Floor are not always right
'Sub rqRound_Problem
'    DefStr sCap = Form.Caption
'    DefInt i = 0, iPrec = 2
'    DefDbl dp = 2.0001, dn = -2.0001
'    DefDbl dpf = 0, dpr = 0, dpc = 0, dnf = 0, dnr = 0, dnc = 0
'    Form.Caption = " Please be patient. This takes a minute to complete."
'    RichEdit.Clear
'    RichEdit.AddString("Precision : 0")
'    RichEdit.AddString("--------------------")
'    For i = 1 to 1001
'      dpf = Floor(dp) : dpr = Round(dp) : dpc = Ceil(dp)
'      dnf = Floor(dn) : dnr = Round(dn) : dnc = Ceil(dn)
'      DoEvents
'      RichEdit.AddString("Floor("+Dbl2Str(dp,iPrec+1,True)+") "+Dbl2Str(dpf,iPrec+3,True))
'      RichEdit.AddString("Round("+Dbl2Str(dp,iPrec+1,True)+") "+Dbl2Str(dpr,iPrec+3,True))
'      RichEdit.AddString(" Ceil("+Dbl2Str(dp,iPrec+1,True)+") "+Dbl2Str(dpc,iPrec+3,True))
'      RichEdit.AddString("Floor("+Dbl2Str(dn,iPrec+1,True)+") "+Dbl2Str(dnf,iPrec+3,True))
'      RichEdit.AddString("Round("+Dbl2Str(dn,iPrec+1,True)+") "+Dbl2Str(dnr,iPrec+3,True))
'      RichEdit.AddString(" Ceil("+Dbl2Str(dn,iPrec+1,True)+") "+Dbl2Str(dnc,iPrec+3,True))
'      RichEdit.AddString(" ")
'      dp = dp - .0010
'      dn = dn + .0010
'    Next i
'    Form.Caption = sCap
'    RichEdit.SelStart = 0
'    dnc = 0 : dnr = 0 : dnf = 0 : dpc = 0 : dpr = 0 : dpf = 0
'    dn = 0 : dp = 0
'    iPrec = 0 : i = 0
'    sCap = ""
'End Sub

