cubic roots using vba - excel

I am lookin for a solution to find cubic roots in Excel. I found the below code at this website.
http://www.mrexcel.com/forum/excel-questions/88804-solving-equations-excel.html
unfortunately, it doesn't work for me - I get #VALUE! when I run it and since I am only learning VBA, I have not had luck debugging it.
Sub QUBIC(P As Double, Q As Double, R As Double, ROOT() As Double)
' Q U B I C - Solves a cubic equation of the form:
' y^3 + Py^2 + Qy + R = 0 for real roots.
' Inputs:
' P,Q,R Coefficients of polynomial.
' Outputs:
' ROOT 3-vector containing only real roots.
' NROOTS The number of roots found. The real roots
' found will be in the first elements of ROOT.
' Method: Closed form employing trigonometric and Cardan
' methods as appropriate.
' Note: To translate and equation of the form:
' O'y^3 + P'y^2 + Q'y + R' = 0 into the form above,
' simply divide thru by O', i.e. P = P'/O', Q = Q'/O',
' etc.
Dim Z(3) As Double
Dim p2 As Double
Dim RMS As Double
Dim A As Double
Dim B As Double
Dim nRoots As Integer
Dim DISCR As Double
Dim t1 As Double
Dim t2 As Double
Dim RATIO As Double
Dim SUM As Double
Dim DIF As Double
Dim AD3 As Double
Dim E0 As Double
Dim CPhi As Double
Dim PhiD3 As Double
Dim PD3 As Double
Const DEG120 = 2.09439510239319
Const Tolerance = 0.00001
Const Tol2 = 1E-20
' ... Translate equation into the form Z^3 + aZ + b = 0
p2 = P ^ 2
A = Q - p2 / 3
B = P * (2 * p2 - 9 * Q) / 27 + R
RMS = Sqr(A ^ 2 + B ^ 2)
If RMS < Tol2 Then
' ... Three equal roots
nRoots = 3
ReDim ROOT(0 To nRoots)
For i = 1 To 3
ROOT(i) = -P / 3
Next i
Exit Sub
End If
DISCR = (A / 3) ^ 3 + (B / 2) ^ 2
If DISCR > 0 Then
t1 = -B / 2
t2 = Sqr(DISCR)
If t1 = 0 Then
RATIO = 1
Else
RATIO = t2 / t1
End If
If Abs(RATIO) < Tolerance Then
' ... Three real roots, two (2 and 3) equal.
nRoots = 3
Z(1) = 2 * QBRT(t1)
Z(2) = QBRT(-t1)
Z(3) = Z(2)
Else
' ... One real root, two complex. Solve using Cardan formula.
nRoots = 1
SUM = t1 + t2
DIF = t1 - t2
Z(1) = QBRT(SUM) + QBRT(DIF)
End If
Else
' ... Three real unequal roots. Solve using trigonometric method.
nRoots = 3
AD3 = A / 3#
E0 = 2# * Sqr(-AD3)
CPhi = -B / (2# * Sqr(-AD3 ^ 3))
PhiD3 = Acos(CPhi) / 3#
Z(1) = E0 * Cos(PhiD3)
Z(2) = E0 * Cos(PhiD3 + DEG120)
Z(3) = E0 * Cos(PhiD3 - DEG120)
End If
' ... Now translate back to roots of original equation
PD3 = P / 3
ReDim ROOT(0 To nRoots)
For i = 1 To nRoots
ROOT(i) = Z(i) - PD3
Next i
End Sub
Function QBRT(X As Double) As Double
' Signed cube root function. Used by Qubic procedure.
QBRT = Abs(X) ^ (1 / 3) * Sgn(X)
End Function
Can anyone please guide me on how to fix it, so I can run it. Thanks.
EDIT: This is how I am running it in Excel (I changed Qubic to be a function instead of sub)
cells A1:A3 contain p,q, r respectively
cells B1:B3 contain Roots()
cells C1:C3 contain array for the output of Qubic
A1:1
A2:1
A3:1
B1:0.1
B2:0.1
B3:0.1
C1:
C2:
C3:
{=QUBIC(A1,A2,A3,B1:B3)}
ADD: now that it works with the fix from #assylias, I am trying the following from another sheet:
Function ParamAlpha(p,q,r) as Double
Dim p as Double
Dim q as Double
Dim r as Double
p=-5
q=-2
r=24
Dim Alpha as Double
Dim AlphaVector() as Double
AlphaVector=QubicFunction(p,q,r)
Alpha=FindMinPositiveValue(AlphaVector)
End Function
Function FindMinPositiveValue(AlphaVector) As Double
Dim N As Integer, i As Integer
N = AlphaVector.Cells.Count
Dim Alpha() As Double
ReDim Alpha(N) As Double
For i = 1 To N
If AlphaVector(i) > 0 Then
Alpha(i) = AlphaVector(i)
Else
Alpha(i) = 100000000000#
End If
Next i
FindMinPositiveValue = Application.Min(Alpha)
End Function
In Excel, I call =ParamAlpha(-5,-2,24) and it returns #VALUE!

If you add the following procedure, it will show the results in a message box. You can then modify it to do something else as you require:
Public Sub test()
Dim p As Double
Dim q As Double
Dim r As Double
Dim roots() As Double
p = 1
q = 1
r = 1
QUBIC p, q, r, roots
Dim i As Long
Dim result As String
result = "("
For i = LBound(roots, 1) To UBound(roots, 1)
result = result & roots(i) & ","
Next i
result = Left(result, Len(result) - 1) & ")"
MsgBox "Roots of y^3 + " & p & ".y^2 + " & r & ".y + " & r & " = 0 has the following roots: " & result
End Sub
Alternatively, if you want the result in the form of a fomula array directly in a spreadsheet, you can add the following function in the same module:
Public Function QubicFunction(p As Double, q As Double, r As Double) As Double()
Dim roots() As Double
QUBIC p, q, r, roots
QubicFunction = roots
End Function
You then call it from Excel by selecting a few cells (horizontally, for example A1:B1) and press CTRL+SHIFT+ENTER:
=QubicFunction(1, 1, 1)

Related

VBA Array is being transposed into the second column of a range, not sure why?

I have the below VBA code which calculates a number of output values and stores them in as an array (beginning_gross_facility_balance being one of the examples).
when I try to print the resulting array values into excel (to output2 range, which is C16:J515 in an excel tab, the array is exported/printed into column D and from row 17.
currently, i = 1 and j = 25
grateful if someone could shine some light on why this is happening/how can I ensure that the output is copied into the first column and first row of the range
Sub AssetProjection2()
Application.ScreenUpdating = False
'pluming variables
Dim i As Integer
Dim j As Integer
Dim Period As Integer
Dim numberOfLoans As Integer
numberOfLoans = WorksheetFunction.CountA(Range("LoanCount")) - 1
ReDim tape(numberOfLoans)
Dim pool_lag As Double
Dim total_gross_facility_limit As Double
Dim beginning_gross_facility_balance(500, 500) As Double
Dim interest_rate As Double
Dim arrangement_fee As Double
Dim admin_fee As Double
Dim Audit_fee As Double
Dim insurance_fee As Double
Dim exit_fee As Double
Dim loan_term As Double
Dim loan_remaining_term As Double
Dim default_flag As String
Dim GDV As Double
'only relevant for loans with no seasoning
Dim first_tranche_percentage As Double
Dim seasoning As Double
Dim adjustment_factor As Double
Dim development_fees As Double
Dim lag As Double
Dim sev As Double
'temps/ output variables on a loan by loan basis (so i can call info from any period and any loan)
Dim pmt As Double
Dim Recovery As Double
Dim TempDefault(500, 500) As Double
'end of period balance is the cumulative gross facility at any given point, at maturity, this should match total gross loan limit
Dim end_of_period_gross_balance(500, 500) As Double
Dim periodic_interest(500, 500) As Double
Dim cumulative_retained_interest(500, 500) As Double
Dim periodic_gross_drawdown(500, 500) As Double
Dim periodic_net_advance(500, 500) As Double
Dim cumulative_net_advance(500, 500) As Double
'the loan redeems in one go, then principal and interest redemptions are split for transparency
Dim total_facility_repayment(500, 500) As Double
Dim principal_redemption(500, 500) As Double
Dim interest_redemption(500, 500) As Double
'pristine/stressed variables
Dim prin As Double
Dim prepay As Double
'scenarios
Dim DefScen As Integer
Dim PrepScen As Integer
Dim SevScen As Integer
Dim LagScen As Integer
Dim IRScen As Integer
'ouput variables
'the below is currently not being used
Dim oBegBalance(500) As Double
Dim oEndBalance(500) As Double
Dim oDefault(500) As Double
Dim oInterest(500) As Double
Dim oPrincipal(500) As Double
Dim oPrepayment(500) As Double
Dim oRecovery(500) As Double
Dim oAccrued(500) As Double
Dim oCumTheoreticalDef(500) As Double
'initialise CF time
Period = 1
pool_lag = Range("total_lag").Value
'this loop will project asset cashflows assuming non-seasonality, then the next loop will look-up the figures for each loan based on the loan's seasonality
For i = 1 To numberOfLoans
SevScen = Range("severity_scen").Cells(i + 1)
LagScen = Range("lag_scen").Cells(i + 1)
'IR scenario currently not in use, when floating interest is modelled, this will be already plugged in
IRScen = Range("IR_scen").Cells(i + 1)
interest_rate = Range("interest_rate").Cells(i + 1)
loan_remaining_term = Range("loan_remaining_term").Cells(i + 1)
loan_term = Range("loan_term").Cells(i + 1)
seasoning = loan_term - loan_remaining_term
first_tranche_percentage = Range("first_tranche_percentage").Cells(i + 1)
total_gross_facility_limit = Range("total_gross_limit").Cells(i + 1)
adjustment_factor = 1.1
admin_fee = Range("admin_fee").Cells(i + 1)
default_flag = Range("default_flag").Cells(i + 1)
For j = 1 To loan_term + pool_lag
lag = Range("LagScenarios").Cells(loan_term + j + 4, LagScen)
sev = Range("severityScenarios").Cells(loan_term + j + 4, SevScen)
If j = 1 Then
arrangement_fee = Range("arrangement_fee").Cells(i + 1)
Audit_fee = Range("Audit_fee").Cells(i + 1)
insurance_fee = Range("insurance_fee").Cells(i + 1)
Else
arrangement_fee = 0
Audit_fee = 0
insurance_fee = 0
End If
If j = loan_term Then
exit_fee = Range("exit_fee").Cells(i + 1)
Else
exit_fee = 0
End If
development_fees = arrangement_fee + Audit_fee + insurance_fee + admin_fee
Recovery = 0
'term is original term, not really used anywhere at the moment, only as a static figure to work out seasonality for input curves
loan_term = Range("loan_term").Cells(i + 1)
'remaining term doesnt need to be dynamic as the PMT formula takes the current J into account to work out the dynamic remaining term
loan_remaining_term = Range("loan_remaining_term").Cells(i + 1)
interest_rate = Range("interest_rate").Cells(i + 1)
If j = 1 Then
beginning_gross_facility_balance(i, j) = total_gross_facility_limit * first_tranche_percentage
Else
beginning_gross_facility_balance(i, j) = end_of_period_gross_balance(i, j - 1)
End If
'gross drawdown. if first disbursment, it's first_tranche_percentage, else, it's a fixed figure such that from month 2 to maturity, the total gross facility equals the gross loan limit. for the model, I will start with a basic number and learn how to apploy a goal seek/solver figure
'draws happen at the beginning of the period and so every period's accrued interest is on the end of period balance J - 1 + period J further draw (J=1 has end of previous period as 0 bcs the loan is new
If j = 1 Then
periodic_gross_drawdown(i, j) = 0
Else
If j < loan_term Then
periodic_gross_drawdown(i, j) = (total_gross_facility_limit - periodic_gross_drawdown(i, 1)) / (loan_term - 2) / adjustment_factor
Else
periodic_gross_drawdown(i, j) = 0
End If
End If
If j = 1 Then
periodic_net_advance(i, j) = beginning_gross_facility_balance(i, j) - development_fees
Else
periodic_net_advance(i, j) = periodic_gross_drawdown(i, j) - development_fees
End If
If j = 1 Then
cumulative_net_advance(i, j) = periodic_net_advance(i, j)
Else
cumulative_net_advance(i, j) = cumulative_net_advance(i, j - 1) + periodic_net_advance(i, j)
End If
periodic_interest(i, j) = (beginning_gross_facility_balance(i, j) + periodic_gross_drawdown(i, j)) * interest_rate
end_of_period_gross_balance(i, j) = beginning_gross_facility_balance(i, j) + periodic_interest(i, j)
If j = loan_term And default_flag = "N" Then
total_facility_repayment(i, j) = end_of_period_gross_balance(i, j)
principal_redemption(i, j) = cumulative_net_advance(i, j)
interest_redemption(i, j) = total_facility_repayment(i, j) - principal_redemption(i, j)
Else
total_facility_repayment(i, j) = 0
principal_redemption(i, j) = 0
interest_redemption(i, j) = 0
End If
If j = loan_term + lag And default_flag = "Y" Then
Recovery = total_facility_repayment(i, j - lag) * (1 - sev) 'accrue some defaulted int rate or keep it simple?
Else
Recovery = 0
End If
Next j
Next i
'write it out
'Range("beginning_balance_output") = WorksheetFunction.Transpose(beginning_gross_facility_balance)
Range("output2") = WorksheetFunction.Transpose(beginning_gross_facility_balance)
' Range("output2").Columns(3) = WorksheetFunction.Transpose(periodic_net_advance)
'Range("output2").Columns(4) = WorksheetFunction.Transpose(cumulative_net_advance)
' Range("output2").Columns(5) = WorksheetFunction.Transpose(total_facility_repayment)(end_of_period_gross_balance)
End Sub
Your problem is that you don't declare the lower bound of the arrays. Per default, VBA set the lower bound to 0, but you don't use the 0th row and/or column in your code.
If you write Dim TempDefault(500, 500) As Double, the 500 is used as upper bound, giving you an array of 501 x 501 (from 0 to 500) elements.
You can do the following:
(a) Declare the arrays like this:
Dim TempDefault(1 to 500, 1 to 500) As Double
(b) Rewrite your code to that it deals with the 0-row and column of the array
(c) Put the following statement at the top of the module:
Option Base 1
That will force the compiler to use 1 as lower bound if omitted at the declaration.
I would advice to use the first option and always declare the lower and upper bounds.

Computing sums on VBA

Trying to sum 2^i from i=0 to i=n on VBA. Where n is an entered value by the user each time
I can get it to do each term individually but its not summing.
Sub Button1()
Dim n As Single
n = InputBox("Enter a value for n")
Dim array as
Dim CSeriesSum As Double
ActiveCell.Value = WorksheetFunction.SeriesSum(Arg1:=2, Arg2:=n, Arg3:=1, Arg4:=1:n)
End Sub
I expect it to return for eg. 1 if n=0, 3 if n=1, 7 if n=2 etc.
Maybe try this loop:
Sub Button1()
Dim n As Single
n = InputBox("Enter a value for n")
Dim val As Long
val = 0
For i = 0 To n
val = val + 2 ^ i
Next
ActiveCell.Value = val
End Sub
In addition to the loop-based approach, you can use the fact that the series
1 + 2 + 2^2 + ... + 2^n + ...
is a geometric series whose partial sums can be expressed in closed-form. In this case the partial sum that you are evaluating is equal to 2^(n+1)-1. For example, if n = 3, it is easy to verify that
1 + 2 + 4 + 8 = 15 = 16-1 = 2^4 - 1
This leads to the following code:
Sub Button1()
Dim n As Long
n = InputBox("Enter a value for n")
ActiveCell.Value = 2 ^ (n + 1) - 1
End Sub

Getting a specific value from excel chart with vba

I am trying to get a specific value from an excel chart. This is the code which creates my chart (I created a reversed binomial distribution plot) :
Dim lim As String
Dim N As Long
N = Range("C4").Value
Dim x, s, p As Double
x = Range("C6") 'event number
s = Range("C5") 'sample size
Dim g() As Long
Dim h() As Double
Dim k() As Double
Dim prob() As Double
ReDim g(N)
ReDim prob(N)
ReDim h(N)
ReDim k(N)
For i = 1 To N
g(i) = i
h(i) = i / N
k(i) = 1 - h(i)
prob(i) = WorksheetFunction.BinomDist(x, s, h(i), False) * 100
End If
And here is chart:
I need the point where y is 0 on distribution curve second time.
At the end of your For Loop, you could check if prob(i) = 0 And Prob(i-1) > 0, and save the index of this point. It's "too" simple, yet if this is just for this kind of distribution, it do the job :
Dim targetIndex As Integer
For i = 1 To N
g(i) = i
h(i) = i / N
k(i) = 1 - h(i)
prob(i) = WorksheetFunction.BinomDist(x, s, h(i), False) * 100
If i > 1 Then 'check if this is not the first point
If prob(i) = 0 And prob(i-1) <> 0 Then targetIndex = i
End If
Next
'// Now your point is the couple (targetIndex, prob(targetIndex))

How to find FFT in Excel without using Fourier Analysis function?

I am trying to write an FFT application in Excel that claculates frequencies, amplitude and phase. I know how to use the in-built function but the data I am trying to analyse has 32,795 points, more than the maximum 4096 for the in-buit function.
Does anyone know how I can either (1) Increase the maximum number of data inputs? (2) Write my own macro to avoid using the in-built function (if this allows me to analyse all the points)? or (3) Start over in Matlab or a with programming language that allows me to analyse all the points and get all the data I need?
You can easily use the matlab built in function and it doesnt have the limitation like Excel and then import the results to excel
Yes, Excel FFT has the limit of data point 4096 and slow.
I programmed FFT using only Excel VBA code and there is no limit of the data point.
Below is the performance for the data point count.
There was a part where I could speed it up a bit, but I didn't b/c it makes the code less readable. However, even now, it may be the fastest FFT code in the Excel.
[data point] [FFT execution time]
4 kB 62ms
16 kB 235ms
64 kB 984ms
Computer Specification: 11th Gen Intel(R) Core(TM) i7-1165G7 # 2.80GHz
I implemented Cooley-Tukey algorithm, and use several techniques to speed-up the code running time in the Excel environment.
You can find the code and download the excel file in here. (https://infograph.tistory.com/351)
Otherwise you can review main logic as below:
'Module: fftProgram
'Author: HJ Park
'Date : 2019.5.18(v1.0), 2022.8.1(v2.0)
Option Explicit
Public Const myPI As Double = 3.14159265358979
Public Function Log2(X As Long) As Double
Log2 = Log(X) / Log(2)
End Function
Public Function Ceiling(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
' X is the value you want to round
' Factor is the multiple to which you want to round
Ceiling = (Int(X / Factor) - (X / Factor - Int(X / Factor) > 0)) * Factor
End Function
Public Function Floor(ByVal X As Double, Optional ByVal Factor As Double = 1) As Double
' X is the value you want to round
' Factor is the multiple to which you want to round
Floor = Int(X / Factor) * Factor
End Function
' return 0 if N is 2^n value,
' return (2^n - N) if N is not 2^n value. 2^n is Ceiling value.
' return -1, if error
Public Function IsPowerOfTwo(N As Long) As Long
If N = 0 Then GoTo EXIT_FUNCTION
Dim c As Long, F As Double
c = Ceiling(Log2(N)) 'Factor=0, therefore C is an integer number
F = Floor(Log2(N))
If c = F Then
IsPowerOfTwo = 0
Else
IsPowerOfTwo = (2 ^ c - N)
End If
Exit Function
EXIT_FUNCTION:
IsPowerOfTwo = -1
End Function
''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''
Function MakePowerOfTwoSize(ByRef r As Range, ByVal fillCount As Long) As Boolean
Dim arr() As Integer
On Error GoTo ERROR_HANDLE
'1)make a array with zero
ReDim arr(0 To fillCount - 1) As Integer
'2)set a range to be filled with zero
Dim fillRowStart As Long
Dim fillRange As Range
fillRowStart = r.Row + r.Rows.Count
Set fillRange = Range(Cells(fillRowStart, r.Column), Cells(fillRowStart + fillCount - 1, r.Column))
'3)fill as zero
fillRange = arr
'4)update range area to be extended
Set r = Union(r, fillRange)
MakePowerOfTwoSize = True
Exit Function
ERROR_HANDLE:
MakePowerOfTwoSize = False
End Function
' read the range and return it as complex value array
Function Range2Array(r As Range) As Complex()
Dim i As Long, size As Long
Dim arr() As Complex
size = r.Rows.Count
ReDim arr(0 To size - 1) As Complex
Dim re As Double, im As Double
On Error GoTo ERROR_HANDLE
For i = 1 To size
arr(i - 1) = String2Complex(r.Rows(i).Value)
Next i
Range2Array = arr
Exit Function
ERROR_HANDLE:
MsgBox "Error: " & i
End Function
Function ArrangedNum(num As Long, numOfBits As Integer) As Long
Dim arr() As Byte
Dim i As Integer, j As Integer
Dim k As Long
If (2 ^ numOfBits) <= num Then GoTo EXIT_FUNCTION
'1) Decimal number -> Reversed Binary array : (13,4) -> {1,1,0,1} -> {1,0,1,1}
ReDim arr(0 To numOfBits - 1) As Byte
For i = 0 To numOfBits - 1
j = (numOfBits - 1) - i
k = Int((num / (2 ^ j)))
arr(j) = (k And 1)
Next i
'2) Reversed Binary -> Decimal: {1,0,1,1} -> 1*2^3 + 0*2^2 + 1*2&1 + 1 = 11
Dim d As Long
For i = 0 To numOfBits - 1
d = d + (arr(i) * 2 ^ (numOfBits - 1 - i))
Next i
ArrangedNum = d
Exit Function
EXIT_FUNCTION:
ArrangedNum = 0
End Function
' rangeArr[1 to n, 1]
Function arrangeToFFTArray(arr() As Complex, size As Long, numOfBits As Integer) As Complex()
Dim i As Long, j As Long
Dim arrangedArr() As Complex
ReDim arrangedArr(0 To size - 1) As Complex
For i = 0 To size - 1
j = ArrangedNum(i, numOfBits) '{000,001,010, 011, 100, 101, 110, 111} -> {0, 4, 2, 6, 1, 5, 3, 7}
arrangedArr(j) = arr(i)
Next i
arrangeToFFTArray = arrangedArr
End Function
' calculate convolution ring W
' W[k] = cos(theta) - isin(theta)
' theta = (2pi*k/N)
Function CalculateW(cnt As Long, isInverse As Boolean) As Complex()
Dim arr() As Complex
Dim i As Long
Dim T As Double, theta As Double
Dim N As Long, N2 As Long
N = cnt
N2 = N / 2
ReDim arr(0 To N2 - 1) As Complex 'enough to calculate 0 to (N/2 -1)
T = 2 * myPI / CDbl(N)
If isInverse Then
For i = 0 To N2 - 1
theta = -(T * i)
arr(i) = Cplx(Cos(theta), -Sin(theta))
Next i
Else
For i = 0 To N2 - 1
theta = T * i
arr(i) = Cplx(Cos(theta), -Sin(theta))
Next i
End If
CalculateW = arr
End Function
' X({0,1}, [0,n-1]): 2d array. (0, n) <--> (1,n)
' src: src index of the array. 0 or 1
' tgt: tgt index of the array. 1 or 0
' s : starting index of the data in the array
' size: region size to be calculated
' kJump : k's jumping value
' W(0 ~ n-1) : Convolution ring
Sub RegionFFT(X() As Complex, src As Integer, tgt As Integer, _
s As Long, size As Long, kJump As Long, W() As Complex)
Dim i As Long, e As Long
Dim half As Long
Dim k As Long
Dim T As Complex
' Xm+1[i] = Xm[i] + Xm[i+half]W[k]
' Xm+1[i+half] = Xm[i] - Xm[i+half]W[k]
k = 0
e = s + (size / 2) - 1
half = size / 2
For i = s To e
T = CMult(X(src, i + half), W(k))
X(tgt, i) = CAdd(X(src, i), T)
X(tgt, i + half) = CSub(X(src, i), T)
k = k + kJump
Next i
End Sub
Sub WriteToTarget(tgtRange As Range, X() As Complex, tgtIdx As Integer, N As Long, roundDigit As Integer)
Dim i As Long
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
For i = 0 To N - 1
If X(tgtIdx, i).im < 0 Then
arr(i) = Round(X(tgtIdx, i).re, roundDigit) & Round(X(tgtIdx, i).im, roundDigit) & "i"
Else
arr(i) = Round(X(tgtIdx, i).re, roundDigit) & "+" & Round(X(tgtIdx, i).im, roundDigit) & "i"
End If
Next i
tgtRange.Rows = Application.Transpose(arr)
End Sub
' xRange: input data
' tgtRange: output range
' isInverse: FFT or IFFT
Public Function FFT_Forward(xRange As Range, tgtRangeStart As Range, roundDigit As Integer, isInverse As Boolean) As Complex()
Dim i As Long, N As Long
Dim totalLoop As Integer, curLoop As Integer 'enough as Integer b/c it is used for loop varoable
Dim xArr() As Complex, xSortedArr() As Complex
Dim W() As Complex 'convolution ring
Dim X() As Complex 'output result
Dim errMsg As String
errMsg = "Uncatched error"
'1) check whether 2^r count data, if not pad to zero
Dim fillCount As Long
N = xRange.Rows.Count
fillCount = IsPowerOfTwo(N)
If fillCount = -1 Then
errMsg = "No input data. Choose input data"
GoTo ERROR_HANDLE
End If
If fillCount <> 0 Then
If MakePowerOfTwoSize(xRange, fillCount) = False Then 'xRange's size will be chnaged
errMsg = "Error while zero padding"
GoTo ERROR_HANDLE
End If
End If
'2) calculate loop count for FFT: 2->1 4->2 8->3 ...
N = xRange.Rows.Count 'xRange's size can be changed so read one more...
totalLoop = Log2(N)
'3) sort x for 2's FFT : convert to reversed binary and then convert to decimal
xArr = Range2Array(xRange) 'xArr[0,n-1]
xSortedArr = arrangeToFFTArray(xArr, N, totalLoop) 'xSortedArr[0,n-1]
'4) calculate W
W = CalculateW(N, isInverse)
'5) use 2-dimensional array to save memory space. X[0, ] <-> X[1, ]
ReDim X(0 To 1, 0 To N - 1) As Complex
For i = 0 To N - 1
X(0, i) = xSortedArr(i)
Next i
'6) Do 2's FFT with sorted x
Dim srcIdx As Integer, tgtIdx As Integer
Dim kJump As Long, regionSize As Long
tgtIdx = 0
For curLoop = 0 To totalLoop - 1
tgtIdx = (tgtIdx + 1) Mod 2
srcIdx = (tgtIdx + 1) Mod 2
regionSize = 2 ^ (curLoop + 1) ' if N=8: 2 -> 4 -> 8
kJump = 2 ^ (totalLoop - curLoop - 1) ' if N=8: 4 -> 2 -> 1
i = 0
Do While i < N
Call RegionFFT(X, srcIdx, tgtIdx, i, regionSize, kJump, W)
i = i + regionSize
Loop
Next curLoop
'7)return the value
Dim resultIdx As Integer
If (totalLoop Mod 2) = 0 Then resultIdx = 0 Else resultIdx = 1
Dim result() As Complex
ReDim result(0 To N - 1) As Complex
If isInverse = True Then
For i = 0 To N - 1
result(i) = CDivR(X(resultIdx, i), N)
Next i
Else
For i = 0 To N - 1
result(i) = X(resultIdx, i)
Next i
End If
FFT_Forward = result
Exit Function
ERROR_HANDLE:
Err.Raise Number:=vbObjectError, Description:=("FFT calculation error: " & errMsg)
End Function
Public Sub FFT(xRange As Range, tgtRangeStart As Range, roundDigit As Integer)
Dim X() As Complex
Dim tgtRange As Range
'1. calculate FFT_forward value
On Error GoTo ERROR_HANDLE
X = FFT_Forward(xRange, tgtRangeStart, roundDigit, False)
'2. write to the worksheet
Dim N As Long
N = UBound(X) - LBound(X) + 1
Dim i As Long
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
For i = 0 To N - 1
If X(i).im < 0 Then
arr(i) = Round(X(i).re, roundDigit) & Round(X(i).im, roundDigit) & "i"
Else
arr(i) = Round(X(i).re, roundDigit) & "+" & Round(X(i).im, roundDigit) & "i"
End If
Next i
Set tgtRange = Range(Cells(tgtRangeStart.Row, tgtRangeStart.Column), Cells(tgtRangeStart.Row + N - 1, tgtRangeStart.Column))
tgtRange.Rows = Application.Transpose(arr)
Exit Sub
ERROR_HANDLE:
End Sub
Public Sub IFFT(xRange As Range, tgtRangeStart As Range, roundDigit As Integer)
Dim X() As Complex
Dim tgtRange As Range
'1. calculate FFT_forward value
On Error GoTo ERROR_HANDLE
X = FFT_Forward(xRange, tgtRangeStart, roundDigit, True)
'2.write to the worksheet
Dim N As Long
N = UBound(X) - LBound(X) + 1
Dim arr() As Variant
ReDim arr(0 To N - 1) As Variant
Dim i As Long
For i = 0 To N - 1
arr(i) = Round(X(i).re, roundDigit)
Next i
Set tgtRange = Range(Cells(tgtRangeStart.Row, tgtRangeStart.Column), Cells(tgtRangeStart.Row + N - 1, tgtRangeStart.Column))
tgtRange.Rows = Application.Transpose(arr)
Exit Sub
ERROR_HANDLE:
End Sub
Sub LoadFFTForm()
FFT_Form.Show
In alternative to the VBA solution from HeeJin, with LAMBDA functions in recent versions of Excel it is possible to implement the FFT as a pure formula (i.e. without VBA).
One such implementation is https://github.com/altomani/XL-FFT.
For power of two length it uses a recursive radix-2 Cooley-Tukey algorithm
and for other length a version of Bluestein's algorithm that reduces the calculation to a power of two case.

Long Datatype Overflow

I am trying to do some prime factorisation with my VBA excel and I am hitting the limit of the long data type -
Runtime Error 6 Overflow
Is there any way to get around this and still stay within VBA? I am aware that the obvious one would be to use another more appropriate programming language.
Lance's solution works in so far that I am able to get the big numbers into the variables now. However, when I try to apply the MOD function - bignumber MOD 2, for example - it still fails with error message
Runtime Error 6 Overflow
You can use Decimal data type. Quick hint from google: http://www.ozgrid.com/VBA/convert-to-decimal.htm
This is my Decimals.cls (VB6):
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
Persistable = 0 'NotPersistable
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "Decimals"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
Attribute VB_Ext_KEY = "SavedWithClassBuilder6" ,"Yes"
Attribute VB_Ext_KEY = "Top_Level" ,"Yes"
Option Explicit
'local variable(s) to hold property value(s)
Private mvarDec As Variant 'local copy
Public Property Let Dec(ByVal vData As Variant)
'used when assigning a value to the property, on the left side of an assignment.
'Syntax: X.Dec = 5
mvarDec = CDec(vData)
End Property
Public Property Get Dec() As Variant
Attribute Dec.VB_UserMemId = 0
'used when retrieving value of a property, on the right side of an assignment.
'Syntax: Debug.Print X.Dec
Dec = CDec(mvarDec)
End Property
and this is a testing program. The class has been setup so that you don't have to qualify with .Dec() on get and let.
Dim dec1 As New Std.Decimals
Dim dec2 As New Std.Decimals
Dim dec3 As New Std.Decimals
Dim modulus As New Std.Decimals
Sub main()
dec1 = "1000.000000001"
dec2 = "1000.00000000000001"
dec3 = dec1 + dec2
Debug.Print dec1
Debug.Print dec2
Debug.Print dec3
Debug.Print dec3 * dec3
Debug.Print dec3 / 10
Debug.Print dec3 / 100
Debug.Print Sqr(dec3)
modulus = dec1 - Int(dec1 / dec2) * dec2
Debug.Print modulus
End Sub
and sample run
1000.000000001
1000.00000000000001
2000.00000000100001
4000000.000004000040000001
200.000000000100001
20.0000000000100001
44.721359550007
0.00000000099999
1000.000000001
1000.00000000000001
2000.00000000100001
4000000.000004000040000001
200.000000000100001
20.0000000000100001
44.721359550007
0.00000000099999
Here is my "big multiply" routine for multiplying arbitrarily large numbers (eg 100 characters long). It works by splitting the input numbers, which are strings, into chunks of 7 digits (because then it can cross multiply them and store the results in Doubles).
eg bigmultiply("1934567803945969696433","4483838382211678") = 8674289372323895422678848864807544574
Function BigMultiply(ByVal s1 As String, ByVal s2 As String) As String
Dim x As Long
x = 7
Dim n1 As Long, n2 As Long, n As Long
n1 = Int(Len(s1) / x + 0.999999)
n2 = Int(Len(s2) / x + 0.999999)
n = n1 + n2
Dim i As Long, j As Long
ReDim za1(n1) As Double
i = Len(s1) Mod x
If i = 0 Then i = x
za1(1) = Left(s1, i)
i = i + 1
For j = 2 To n1
za1(j) = Mid(s1, i, x)
i = i + x
Next j
ReDim za2(n2) As Double
i = Len(s2) Mod x
If i = 0 Then i = x
za2(1) = Left(s2, i)
i = i + 1
For j = 2 To n2
za2(j) = Mid(s2, i, x)
i = i + x
Next j
ReDim z(n) As Double
Dim u1 As Long, u2 As Long
Dim e As String
e = String(x, "0")
For u1 = 1 To n1
i = u1
For u2 = 1 To n2
i = i + 1
z(i) = z(i) + za1(u1) * za2(u2)
Next u2
Next u1
Dim s As String, y As Double, w As Double, m As Long
m = n * x
s = String(m, "0")
y = 10 ^ x
For i = n To 1 Step -1
w = Int(z(i) / y)
Mid(s, i * x - x + 1, x) = Format(z(i) - w * y, e)
z(i - 1) = z(i - 1) + w
Next i
'truncate leading zeros
For i = 1 To m
If Mid$(s, i, 1) <> "0" Then Exit For
Next i
If i > m Then
BigMultiply = ""
Else
BigMultiply = Mid$(s, i)
End If
End Function
MOD is trying to convert your DECIMAL type to LONG before operating on it. You may need to write your own MOD function for the DECIMAL type. You might try this:
r = A - Int(A / B) * B
where A & B are DECIMAL subtype of VARIANT variables, and r might have to be that large also (depending on your needs), though I only tested on a long.

Resources