Simple Histogram in VBA? - excel

I have data stored in some column (Say, Column A). The length of Column A is not fixed (depends on previous steps in the code).
I need a histogram for the values in Column A, and have it in the same sheet. I need to take the values in column A, and automatically compute M Bins, then give the plot.
I looked online for a "simple" code, but all codes are really fancy, with tons of details that I don't need, to the extent that I am not even able to use it. (I am a VBA beginner.)
I found the following code that seems to do the job, but I am having trouble even calling the function. Besides, it only does computations but does not make the plot.
Sub Hist(M As Long, arr() As Single)
Dim i As Long, j As Long
Dim Length As Single
ReDim breaks(M) As Single
ReDim freq(M) As Single
For i = 1 To M
freq(i) = 0
Next i
Length = (arr(UBound(arr)) - arr(1)) / M
For i = 1 To M
breaks(i) = arr(1) + Length * i
Next i
For i = 1 To UBound(arr)
If (arr(i) <= breaks(1)) Then freq(1) = freq(1) + 1
If (arr(i) >= breaks(M - 1)) Then freq(M) = freq(M) + 1
For j = 2 To M - 1
If (arr(i) > breaks(j - 1) And arr(i) <= breaks(j)) Then freq(j) = freq(j) + 1
Next j
Next i
For i = 1 To M
Cells(i, 1) = breaks(i)
Cells(i, 2) = freq(i)
Next i
End Sub
And then I try to call it simply by:
Sub TestTrial()
Dim arr() As Variant
Dim M As Double
Dim N As Range
arr = Range("A1:A10").Value
M = 10
Hist(M, arr) ' This does not work. Gives me Error (= Expected)
End Sub

A little late but still I want to share my solution. I created a Histogram function which might be used as array formula in the excel spread sheet. Note: you must press
CTRL+SHIFT+ENTER to enter the formula into your workbook. Input is the range of values and the number M of bins for the histogram. The output range must have M rows and two columns. One column for the bin value and one column for the bin frequency.
Option Explicit
Option Base 1
Public Function Histogram(arr As Range, M As Long) As Variant
On Error GoTo ErrHandler
Dim val() As Variant
val = arr.Value
Dim i As Long, j As Integer
Dim Length As Single
ReDim breaks(M) As Single
ReDim freq(M) As Integer
Dim min As Single
min = WorksheetFunction.min(val)
Dim max As Single
max = WorksheetFunction.max(val)
Length = (max - min) / M
For i = 1 To M
breaks(i) = min + Length * i
freq(i) = 0
Next i
For i = 1 To UBound(val)
If IsNumeric(val(i, 1)) And Not IsEmpty(val(i, 1)) Then
If val(i, 1) > breaks(M) Then
freq(M) = freq(M) + 1
Else
j = Int((val(i, 1) - min) / Length) + 1
freq(j) = freq(j) + 1
End If
End If
Next i
Dim res() As Variant
ReDim res(M, 2)
For i = 1 To M
res(i, 1) = breaks(i)
res(i, 2) = freq(i)
Next i
Histogram = res
ErrHandler:
'Debug.Print Err.Description
End Function

Not 100% sure as to the efficacy of that approach but;
Remove the parens as your calling a sub; Hist M, arr
M is declared as double but received by the function as a long; this won't work so declare it in the calling routine as long
You will need to recieve arr() As Variant
Range -> Array produces a 2 dimensional array so the elements are arr(1, 1) .. arr(n, 1)

Related

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)

Select random item from 1d array no repeat

I am trying to select random item from 1d array using this code
Sub Select_Random_Item_From_1D_Array()
Dim arr(), x As Long
arr = Array("Good", "Very Good", "Excellent")
Randomize
x = Int((UBound(arr) + 1) * Rnd + 1)
Debug.Print arr(x - 1)
End Sub
How can I be able to prevent a repetition? I mean I need to select all the items randomly with no repetition. And if all the items are selected then to reset the process. Simply I need to select all the items randomly
This is a simple way to return a random permutation of an array that takes exactly n steps, where n is the number of entries in the array.
Dim arr(), x As Long, r As Long
arr = Array("Good", "Very Good", "Excellent")
x = UBound(arr)
While x >= 0
r = Int(Rnd * x)
Debug.Print arr(r)
arr(r) = arr(x)
x = x - 1
Wend
Pick r at random from (0,..,x) and print out arr(r). Then replace the entry at r with the entry at x, and choose again, but this time from (0,..,x-1), and repeat until x=0.
A fuller version that lets you read one entry at a time is here:
Place this in a module:
Public rarr(), ctr As Integer, arr()
Sub init()
With Cells
.Clear
.ColumnWidth = 10
End With
Dim x As Long, r As Long
arr = Array("Very Poor", "Poor", "Average", "Good", "Very Good", "Excellent")
x = UBound(arr)
ReDim rarr(0 To x)
Randomize
While x >= 0
r = Int(Rnd * x)
rarr(x) = arr(r)
arr(r) = arr(x)
x = x - 1
Wend
[a1:f1] = rarr
ctr = 0
End Sub
Sub Button1_Click()
Cells(ctr + 3, 1) = rarr(ctr)
ctr = ctr + 1
If ctr > UBound(rarr) Then init
End Sub
and add two buttons to the worksheet. Point one at init and the other at Button1_Click. Click init first, and then pressing Button1 displays a random and unique entry one at a time.
You could create a second array of Booleans, having the same length.
This array is initialized with only False. If a value from your array is selected by the rand, then set the boolean array matching value to True. And if the rand value next time is on a value which has already been selected (with a True in the boolean array), do the rand again
Try this little example step by step, you'll see the logic:
Sub Select_Random_Item_From_1D_Array()
Dim arr(), x As Long, cpt As Long
Dim mBool(2) As Boolean
cpt = 0
arr = Array("Good", "Very Good", "Excellent")
Do While cpt < 3 '3 being the number of items in your array + 1 (from 0 to 2)
Randomize
x = Int((UBound(arr) + 1) * Rnd + 1)
If mBool(x - 1) = False Then
mBool(x - 1) = True
Debug.Print arr(x - 1)
cpt = cpt + 1
End If
Loop
End Sub
It will print a random item from your array, and every time it does so it changes the matching value of the 2nd array from False to True. Then it does it again and if it has already been printed (if the matching value on the boolean array is True) it tries again.
I added a variable named cpt, which goes from 0 to the number of items in your array, it makes the algorithm stop when it has printed all the items one time.
This is probably not he best way to do what you want, but it works and it's not that complicated
Select Random Item Series
Option Explicit
Sub resetRandomItem()
getRandomItem
End Sub
Sub selectRandomItem()
Dim arr As Variant
arr = Array("Bad", "Better", "Good", "Very Good", "Excellent")
Debug.Print getRandomItem(arr)
End Sub
' If x elements in 1D array, it returns a series of x different values.
Function getRandomItem(Optional Data1D As Variant) As Variant
Static arr As Variant
' Reset 'arr': use 'getRandomItem' without 'Data1D' parameter.
If IsMissing(Data1D) Then arr = Empty: Exit Function
If IsEmpty(arr) Then arr = Data1D
Dim lb As Long: lb = LBound(arr)
Dim ub As Long: ub = UBound(arr)
If lb = ub Then
getRandomItem = arr(lb)
arr = Empty
Else
Randomize
Dim x As Long: x = Int((ub - lb + 1) * Rnd + 1)
Dim y As Long: y = x + lb - 1
getRandomItem = arr(y)
arr(y) = Empty
Dim i As Long, k As Long
For i = lb To ub - 1
If arr(i) = Empty Then
For k = i + 1 To ub
arr(k - 1) = arr(k)
Next k
Exit For
End If
Next i
ReDim Preserve arr(ub - 1)
End If
End Function

Searching a table both horizontally and vertically and printing the values

sorry for the ambiguous title. I'm not quite sure how to name what I'm trying to do.
I have data in a worksheet that looks like this:
I would like to search for the smallest number in the range and write out the name on the y axis and the number. It then ignores this number and searches for the smallest number on the x axis as well. In that same row, it searches for the smallest value horizontally, excludes the number and then looks vertically as well. It continues this way until all possibilities are exhausted. Is this possible with Excel?
A sample output will be:
y5 : 40
x3: 60
y3: 90
x4: 80
y2 : 85
x3: 75
y1 : 70
and so on.
Interesting problem. You should be able to modify the following. To run it you need to include a reference to Microsoft Scripting Runtime (Under Tools/References in the VBA editor) since it uses a dictionary data structure -- the natural choice to keep track of already picked numbers:
'The following code assumes than Nums is a 1-based 2-dimensional array
Function MinPath(Nums As Variant) As Variant
Dim counter As Long
Dim mins As Variant
Dim PickedNums As New Dictionary
Dim i As Long, j As Long, m As Long, n As Long
Dim report As String
Dim direction As String
Dim num As Variant
Dim min As Variant, min_i As Long, min_j As Long
m = UBound(Nums, 1)
n = UBound(Nums, 2)
ReDim mins(1 To m * n)
min = Nums(1, 1)
min_i = 1
min_j = 1
For i = 1 To m
For j = 1 To n
If Nums(i, j) < min Then
min = Nums(i, j)
min_i = i
min_j = j
End If
Next j
Next i
PickedNums.Add min, 0
counter = 1
mins(counter) = Array(min_i, min_j, min)
direction = "vertical"
min = Empty
Do While True
If direction = "vertical" Then
For i = 1 To m
num = Nums(i, min_j)
If Not PickedNums.Exists(num) Then
If IsEmpty(min) Then
min = num
min_i = i
ElseIf num < min Then
min = num
min_i = i
End If
End If
Next i
If IsEmpty(min) Then
ReDim Preserve mins(1 To counter)
MinPath = mins
Exit Function
Else
PickedNums.Add min, 0
counter = counter + 1
mins(counter) = Array(min_i, min_j, min)
direction = "horizontal"
End If
Else
'direction = horizontal case
For j = 1 To n
num = Nums(min_i, j)
If Not PickedNums.Exists(num) Then
If IsEmpty(min) Then
min = num
min_j = j
ElseIf num < min Then
min = num
min_j = j
End If
End If
Next j
If IsEmpty(min) Then
ReDim Preserve mins(1 To counter)
MinPath = mins
Exit Function
Else
PickedNums.Add min, 0
counter = counter + 1
mins(counter) = Array(min_i, min_j, min)
direction = "vertical"
End If
End If
min = Empty
Loop
End Function
The function repeatedly searches a either a row or a column (depending on the search direction) to find the smallest non-picked number. At the beginning of each pass the variable min is set to Empty until a non-picked number is encountered. If after a pass min is still Empty the function returns. This function returns an array of arrays where each array is of the form Array(i,j,min) (e.g. the values (5,3,40) in the first step). VBA's Array function returns a 0-based array so the i (row) coordinate is at index 0 and the j coordinate is at index 1. What you do with this data is up to you. For example:
Sub test()
Dim i As Long, n As Long
Dim mins As Variant
mins = MinPath(Range("B2:F6").Value)
n = UBound(mins)
For i = 1 To n
If i Mod 2 = 1 Then 'odd step
Range("A7").Offset(i).Value = "y" & mins(i)(0) & ":"
Else 'even step
Range("A7").Offset(i).Value = "x" & mins(i)(1) & ":"
End If
Range("B7").Offset(i).Value = mins(i)(2)
Next i
End Sub
Which results in:

#Name and VBA function

Creating a new formula to more easily deal with weighted median. Function works as a sub but not as a Function. So values are parsed in the first range and weights in the second i.e. {10,20,15,30,15}, {1,2,0,3,4}
Not sure were the error is creaping in.
Function weightedMedian(valueRng As Range, weightRng As Range)
Dim weightValueArr(), valueArr() As Double
ReDim weightValueArr(Application.Sum(weightRng) - 1)
ReDim valueArr(valueRng.Rows.Count - 1)
For k = 0 To valueRng.Rows.Count - 1
valueArr(k) = valueRng(k + 1)
Next
Dim n, m As Integer
n = 0
m = 0
For Each j In weightRng
For i = 0 To j - 1
weightValueArr(m) = valueArr(n)
m = m + 1
Next
n = n + 1
Next
weightedMedian = Application.Median(weightValueArr)
End Function
You need to give the type of your function, unlike a sub, right at the start.
Function weightedMedian(valueRng As Range, weightRng As Range) As double
Of course, this is the only way to know what the type of value returned IS, since you never dim it or receive it as an argument.

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