VB multi-dimensional array throwing out of bounds exception - excel

I'm currently working on a small piece of vba software that is doing some excel worksheet manipulation. I'm currently running into an indexOutOfRangeException during the first iteration of the loop in the code below. I've run this in debug and can see that the array has a value at the requested index but I keep getting this exception.
Private Sub AddReportQuestions(ByRef ReportName As String, ByRef i As Integer,
ByRef domainStart As Integer, ByRef domainEnd As Integer,
ByRef xlReportWorkSheet As Excel.Worksheet,
ByRef xlQuestionWorkSheet As Excel.Worksheet)
Dim row As Integer
row = 0
Dim maxPrintableRow = ((domainEnd - domainStart) * 4) + 7
Dim maxQuestions = (domainEnd - domainStart) + 1
Dim reportRange = xlReportWorkSheet.Range(xlReportWorkSheet.Cells(7, (i * 10) + 1), xlReportWorkSheet.Cells(maxPrintableRow, (i * 10) + 10))
Dim questionRange = xlQuestionWorkSheet.Range("C" & domainStart & ":I" & domainEnd)
Dim questionArray(maxQuestions, 7) As Object
Dim reportArray(maxPrintableRow + 1, 10) As Object
questionArray = questionRange.Value
For j = 0 To maxQuestions
' Question
reportArray(row, 0) = questionArray(j, 0)
' Resize Row Height
xlReportWorkSheet.Rows(row + 7).RowHeight = 45
row += 1
' Response and Comment
reportArray(row, 0) = questionArray(j, 1)
reportArray(row, 2) = questionArray(j, 4)
' Resize Row Height
xlReportWorkSheet.Rows(row + 7).RowHeight = 45
row += 1
' Likelihood and Consequence
reportArray(row, 0) = "Likelihood:"
reportArray(row, 2) = questionArray(j, 2)
reportArray(row, 4) = "Consequence:"
reportArray(row, 6) = questionArray(j, 3)
row += 1
' Divider line
reportArray(row, 0) = "________________________________________________________________________________________________________________________"
row += 1
Next
reportRange.Value = reportArray
End Sub
questionArray is the offender in this code.

You are declaring questionArray and reportArray as objects, however you are treating them like variables. Change your declaration statements to
Dim questionArray as variant
Dim reportArray(maxPrintableRow + 1, 10) As String

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.

VBA Rolling Mean

I am very new to VBA. I am trying to calculate the rolling means of a range, I certain my mistake is something very silly
Function Rolling_Mean(Prices as Range)
Dim window as Long, i As Integer, temp_sum as Long
Dim means() as Long
window = 10
temp_sum = 0
ReDim means(1 to 253)
For i = 1 to 253
temp_sum = temp_sum + Prices(i)
If i Mod window = 0 Then
means(i) = temp_sum / 10
temp_sum = 0
End If
Next
Rolling_Mean = means
End Function
I keep getting an error or an array of 0s. I think my issue is how im trying to access the Prices. I am also wondering how to implement the syntax AVERAGE(Prices(1) to Prices(10)) as that would help a lot as well.
This will be run in the sheet with =Rolling_Mean(B2:B253)
This seems to work for me. Added the window as a second parameter.
Function Rolling_Mean(Prices As Range, window As Long)
Dim i As Long, sum As Double
Dim means(), data
data = Prices.Value 'assuming a single column of cells....
ReDim means(1 To UBound(data, 1), 1 To 1)
For i = 1 To UBound(data, 1)
sum = sum + data(i, 1)
If i >= window Then
means(i, 1) = sum / window
sum = sum - data(i - window + 1, 1) 'subtract value from trailing end of window
Else
means(i, 1) = ""
End If
Next i
Rolling_Mean = means
End Function
If your Excel version doesn't have "auto spill" then you'll need to enter it as an array formula (Ctrl+Shift+Enter)

I made a sub a function but get an error, what's wrong?

I wrote a sub in VBA for excel. i did that to debug and step through the code. I changed it to a function and only changed the Dims and the last line for the output, it get an error on the spread sheet, What am I doing wrong? I commented out the dims needed when it was a sub
Function CostPerTon(FeedType As String) As Double
'Dim FeedType As String, count#, CostPerTon#
Dim count#
'FeedType = "Duck - Comercial - Starter - 1 to 25 Days"
Sheets("Formulations").Activate
count = Application.WorksheetFunction.CountA(Range("A:A"))
i = 1
Dim IngredientCost As Double
Dim MyArray(100) As Variant
Do While i < count + 1
IngredientCost = Application.WorksheetFunction.VLookup(FeedType, Range("1:1048576"), i + 2, 0) _
* Worksheets("Costs").Cells(i + 2, 2) * (1 / 1000)
MyArray(i) = IngredientCost
i = i + 1
Loop
CostPerTon = WorksheetFunction.Sum(MyArray)
End Function

Array of ranges no longer being set successfully after adding a range declaration on the same line

The code is falling down on the line If snake(i).Column = 10 Then
with the Runtime error 424, Object required. Upon checking for the existence of the range snake(i) where i = 3, there is none, but I cannot see why this range array and the other one that it relies on, box(), are not being set properly.
Context: As a personal challenge and to improve my understanding of VBA, I recreated a game of Snake from scratch in Excel. It was working perfectly fine until I started to add in some code, seen at the very bottom of the quote below, in order to add the occasional greater-value food items. I don't understand how code from further down can stop code further up from functioning how it used to. The only changes made to the code above the error line since the working version are the two variables applesEaten and gAppleCountdown which seem to me completely straightforward. Edit: I also added the declaration of the range goldenApple, which it turned out is the cause of the error.
Dim wb As Workbook
Dim ws, scoreSheet As Worksheet
Set wb = ActiveWorkbook
Set ws = wb.Worksheets(1)
Set scoreSheet = wb.Worksheets(2)
Dim box(1 To 100) As Range
Dim vert, hori, i, step, re, rand, randResult, timeLoop, snakeHeadCol, snakeHeadRow, score, applesEaten, gAppleCountdown, speed, lastRow As Long
Dim snake(), goldenApple As Range
ReDim snake(1 To 3)
re = 3
speed = scoreSheet.Cells(2, 12).Value
applesEaten = 0
gAppleCountdown = 0
score = 0
For vert = 1 To 10
For hori = 1 To 10
Set box(hori + 10 * (vert - 1)) = ws.Cells(vert, hori)
Next
Next
For i = 1 To 100
box(i) = ""
Next
Set snake(1) = box(44)
Set snake(2) = box(45)
Set snake(3) = box(46)
For i = LBound(snake) To UBound(snake)
snake(i) = 1
Next
box(49) = 2
step = 1
Do Until WorksheetFunction.Sum(ws.Range("A1:J10")) >= 101
i = UBound(snake)
Select Case direction
Case 1
If snake(i).Column = 10 Then
If snake(i).Offset(0, -9) = 2 Or snake(i).Offset(0, -9) = 0.2 Then
If snake(i).Offset(0, -9).Address = goldenApple.Address Then
score = score + gAppleCountdown
Set goldenApple = ws.Cells(11, 11)
Else
score = score + 1
applesEaten = applesEaten + 1
rand = Int((100 - 1 + 1) * Rnd + 1)
randResult = rand
Do While box(randResult) = 1
rand = Int((100 - 1 + 1) * Rnd + 1)
randResult = rand
Loop
box(randResult) = 2
End If
re = re + 1
ReDim Preserve snake(1 To re)
Set snake(re) = snake(re - 1).Offset(0, -9)
snake(re) = 1
If applesEaten Mod 5 = 0 Then
rand = Int((100 - 1 + 1) * Rnd + 1)
randResult = rand
Do While box(randResult) = 1
rand = Int((100 - 1 + 1) * Rnd + 1)
randResult = rand
Loop
Set goldenApple = box(randResult)
goldenApple = 0.2
gAppleCountdown = 5
ws.Cells(1, 11) = gAppleCountdown
Else
End If
ws.Cells(23, 5) = score
With thanks to Damian, for explaining in the comments, the problem was the line
Dim snake(), goldenApple As Range
This is equivalent to
Dim snake()
Dim goldenApple As Range
when what is really required is
Dim snake() As Range, Dim goldenApple As Range
or
Dim snake() As Range
Dim goldenApple As Range
Declarations of type Dim snake() by default provide for variants.

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.

Resources