You are on page 1of 6

Attribute VB_Name = "xlInterpolators"

Option Explicit
' Linear and bilinear inter/extrapolators
' shg 1997-2012
'
'
'

Function
Use
---------- -------LInterp
UDF only
of ranges)
' dLInterp
VBA only

Purpose
------UDF wrapper for dLinterp (linear inter/extrapolation

' BiLInterp UDF only


tion of ranges)
' dBiLInterp VBA only

UDF wrapper for dBiLInterp (bilinear inter/extrapola

Linear inter/extrapolator

VBA bilinear inter/extrapolator

'

bInterp
VBA only
Interpolates the non-numeric values of a 1D array in
situ
' InterpArr UDF or VBA UDF wrapper for bInterp
' Sub Interpolate
Interpolates the selected range in situ
' Internal functions
' Frac
VBA only
' NumDim
VBA only

Computes an interpolation fraction


Returns the number of dimensions of an array

Private Function Frac(d As Double, _


avd As Variant, _
ByRef i As Long, _
ByRef dF As Double, _
iMatchType As Long)
' shg 1997-0606, 2009-0419
'
2009-0604 added option for descending sort
' Returns an index to avd in i and an interpolation fraction in dF
' avd must be a 2+ element vector sorted {a|de}scending if iMatchType={1|-1}
If iMatchType = 1 And d <= avd(1) Or _
iMatchType = -1 And d >= avd(1) Then
i = 1
Else
' this can generate an error, handled by caller
i = WorksheetFunction.Match(d, avd, iMatchType)
If i = UBound(avd) Then i = UBound(avd) - 1
End If
dF = (d - avd(i)) / (avd(i + 1) - avd(i))
End Function
Function LInterp(x As Double, rX As Range, rY As Range) As Variant
' UDF wrapper for dLInterp
' shg 1997-0606, 2009-0419
'
2009-0604 added option for descending sort
' rX and rY must be equal-length vectors
' rX must be sorted (ascending or descending, doesn't matter)
Dim avdX
Dim avdY

As Variant
As Variant

On Error GoTo Oops


With WorksheetFunction
If rX.Areas.Count > 1 Then Err.Raise xlErrValue
If rX.Rows.Count <> 1 And rX.Columns.Count <> 1 Then Err.Raise xlErrValu
e
If .Count(rX) <> rX.Cells.Count Then Err.Raise xlErrValue
If rY.Areas.Count > 1 Then Err.Raise xlErrValue
If rY.Rows.Count <> 1 And rY.Columns.Count <> 1 Then Err.Raise xlErrValu
e
If .Count(rY) <> rY.Cells.Count Then Err.Raise xlErrValue
If rX.Cells.Count < 2 Then Err.Raise xlErrValue
If rX.Cells.Count <> rY.Cells.Count Then Err.Raise xlErrValue
If rX.Rows.Count > 1 Then avdX = .Transpose(rX.Value2) Else avdX = .Tran
spose(.Transpose(rX.Value2))
If rY.Rows.Count > 1 Then avdY = .Transpose(rY.Value2) Else avdY = .Tran
spose(.Transpose(rY.Value2))
LInterp = dLInterp(x, avdX, avdY)
End With
Exit Function
Oops:
LInterp = CVErr(Err.Number)
End Function
Function dLInterp(x As Double, avdX As Variant, avdY As Variant) As Double
' Linear interpolator / extrapolator
' {Inter|extra}polates avdX to return the value of avdY corresponding to the
given x
Dim i
Dim dF

As Long
As Double

' index to rY
' interpolation fraction

Frac x, avdX, i, dF, Sgn(avdX(UBound(avdX)) - avdX(1))


dLInterp = avdY(i) * (1 - dF) + avdY(i + 1) * dF
End Function
Public Function BiLInterp(x As Double, _
y As Double, _
rTbl As Range) As Variant
' shg 2007
' UDF wrapper for BiLInterp
Dim rX
Dim rY

As Range
As Range

Dim avdX
Dim avdY
Dim avdZ

As Variant
As Variant
As Variant

' top row of rTbl


' left column of rTbl

On Error GoTo Oops


If rTbl.Areas.Count > 1 Then Err.Raise xlErrValue
If rTbl.Rows.Count < 3 Or rTbl.Columns.Count < 3 Then Err.Raise xlErrValue
With WorksheetFunction
' If .Count(rTbl) - .Count(rTbl(1)) <> rTbl.Cells.Count - 1 Then Err.Rai

se xlErrValue
Set rX = Range(rTbl(1, 2), rTbl(1, rTbl.Columns.Count))
Set rY = Range(rTbl(2, 1), rTbl(rTbl.Rows.Count, 1))
avdX = .Transpose(.Transpose(rX.Value))
avdY = .Transpose(rY.Value)
avdZ = Range(rTbl(2, 2), rTbl(rTbl.Rows.Count, rTbl.Columns.Count)).Valu
e2
BiLInterp = dBiLInterp(x, y, avdX, avdY, avdZ)
End With
Exit Function
Oops:
BiLInterp = CVErr(Err.Number)
End Function
Function dBiLInterp(x As Double, y As Double, _
avdX As Variant, avdY As Variant, _
avdZ As Variant)
' shg 1997-0606, 2007-0307, 2009-0419
'
2009-0615 added support for ascending or descending sort
'
2009-1021 changed implementation of weighted sum
'
2011-1228 fix a bug in computing iMatchType!
' Returns the bilinear interpolation of rTbl with
' o x interpolated across the top row (rX)
' o y interpolated down the left column (rY)
' x and y must be within the upper and lower limits of rX and rY
' rTbl must be sorted
' o
left to right by the top row
' o
top to bottom by the left column
' The sort orders can be ascending or descending,
' each independent of the other.
' All values in rTbl must be numeric (other than the UL corner,
' which is ignored).
Dim
Dim
Dim
Dim

iRow
iCol
dRF
dCF

As
As
As
As

Long
Long
Double
Double

' row fraction


' column fraction

Frac x, avdX, iCol, dCF, Sgn(avdX(UBound(avdX)) - avdX(1))


Frac y, avdY, iRow, dRF, Sgn(avdY(UBound(avdY)) - avdY(1))
' weighted sum of four
dBiLInterp = avdZ(iRow
avdZ(iRow
avdZ(iRow
avdZ(iRow
End Function

corners
+ 0, iCol
+ 0, iCol
+ 1, iCol
+ 1, iCol

+
+
+
+

0)
1)
0)
1)

*
*
*
*

(1# - dRF)
(1# - dRF)
(dRF - 0#)
(dRF - 0#)

*
*
*
*

(1# - dCF) + _
(dCF - 0#) + _
(1# - dCF) + _
(dCF - 0#)

'===============================================================================
Function bLInterp(avInp As Variant) As Boolean
' shg 2010, 2012
' VBA only

'
'
'
'

Interpolates between the numeric values in 1D array avInp in situ


Non-numeric values (Empty, text, logical, or errors)
are replaced by linear interpolation or extrapolation of the adjacent
numeric values.

' Returns False if avInp is not 1D, or contains fewer than two numbers.
' UDF or VBA
' For example =InterpArr({"", "", 1, "", 3, "", "", 9, ""})
'
returns
{-1, 0, 1, 2, 3, 5, 7, 9, 11}
' Requires NumDim
Dim
Dim
Dim
Dim
Dim
Dim

iLB
iUB
iInp
nNum
aiNum()
iNum

Dim dF

As
As
As
As
As
As

Long
Long
Long
Long
Long
Long

As Double

'
'
'
'
'
'

lower bound of avInp


upper bound of avInp
index to avInp
count of numbers in avInp
stores the indices of numeric values in avInp
index to aiNum

' interpolating fraction

If NumDim(avInp) > 1 Then Exit Function


With WorksheetFunction
nNum = .Count(avInp)
If nNum < 2 Then Exit Function
ReDim aiNum(1 To nNum)
iLB = LBound(avInp)
iUB = UBound(avInp)
For iInp = iLB To iUB
If .IsNumber(avInp(iInp)) Then
iNum = iNum + 1
aiNum(iNum) = iInp
End If
Next iInp
End With
iNum = 1
For iInp = iLB To iUB
If iInp > aiNum(iNum + 1) Then
If iNum < nNum - 1 Then iNum = iNum + 1
End If
dF = (iInp - aiNum(iNum)) / (aiNum(iNum + 1) - aiNum(iNum))
avInp(iInp) = (1 - dF) * avInp(aiNum(iNum)) + dF * avInp(aiNum(iNum + 1)
)
Next iInp
bLInterp = True
End Function
Function InterpArray(avInp As Variant) As Variant
' shg 2010, 2012
' UDF wrapper for bInterp
Dim av

As Variant

Dim r

As Range

Set r = Application.Caller
If r.Areas.Count > 1 Or (r.Rows.Count > 1 And r.Columns.Count > 1) Then
InterpArray = CVErr(xlErrValue)
Else
With WorksheetFunction
av = avInp
If NumDim(av) > 1 Then av = .Transpose(av)
If NumDim(av) > 1 Then av = .Transpose(av)
If NumDim(av) > 1 Then
InterpArray = CVErr(xlErrValue)
Else
bLInterp av
If r.Rows.Count = 1 Then
InterpArray = av
Else
InterpArray = .Transpose(av)
End If
End If
End With
End If
End Function
Sub Interpolate()
' shg 2012
' Interpolates the selected range in situ
' Requires bLInterp
Dim r
As Range
Dim av
As Variant
If TypeOf Selection Is Range Then
Set r = Selection
If r.Areas.Count > 1 Or (r.Rows.Count > 1 And r.Columns.Count > 1) Then
MsgBox "Must select a single-area, single-row or single-column range
!"
Else
With WorksheetFunction
If r.Rows.Count = 1 Then
av = .Transpose(.Transpose(r.Value))
bLInterp av
r.Value = av
Else
av = .Transpose(r.Value)
bLInterp av
r.Value = .Transpose(av)
End If
End With
End If
Else
MsgBox "Must select a range!"
End If
End Sub

Private Function NumDim(av As Variant) As Long


Dim i
As Long
If TypeOf av Is Range Then
If av.Count = 1 Then NumDim = 1 Else NumDim = 2
ElseIf IsArray(av) Then
On Error GoTo Done
For NumDim = 0 To 6000
i = LBound(av, NumDim + 1)
Next NumDim
Done:
Err.Clear
End If
End Function

You might also like