How to calculate/define ArcSin function in VBA? - vba

How to implement the VBA code of the arcsin function (defined below)?
Definition: The arcsin function is the inverse of the sine function. It returns the angle whose sine is a given number. For every trigonometry function, there is an inverse function that works in reverse. These inverse functions have the same name but with 'arc' in front. (On some calculators the arcsin button may be labelled asin, or sometimes sin-1.) So the inverse of sin is arcsin etc. When we see "arcsin A", we understand it as "the angle whose sin is A"
sin30 = 0.5 Means: The sine of 30 degrees is 0.5
arcsin 0.5 = 30 Means: The angle whose sin is 0.5 is 30 degrees.

I don't really understand your question here. The arcsin function already exists in VBA, you can use it with :
WorksheetFunction.Asin(myValue)
Use of the arcsin function
Dim myValue As Double
myValue = 0.1234
MsgBox CStr(WorksheetFunction.Asin(myValue))
There you can print the result of the arcsin function for a value as Double.

The following code will help to implement the ARCSIN function based on given definition:
Option Explicit
Public Const Pi As Double = 3.14159265358979
Private Function ArcSin(X As Double) As Double
If Abs(X) = 1 Then
'The VBA Sgn function returns an integer (+1, 0 or -1),
'representing the arithmetic sign of a supplied number.
ArcSin = Sgn(X) * Pi / 2
Else
'Atn is the inverse trigonometric function of Tan,
'which takes an angle as its argument and returns
'the ratio of two sides of a right triangle
ArcSin = Atn(X / Sqr(1 - X ^ 2))
End If
End Function

Related

Calculating distance between 2 coordinates in MS Access

Currently I am trying to populate the distance between 2 coordinates in Access. I found a VBA function online that allows me to use ACOR and RADIANS in access. The function worked, but when I attempt to "Make Table", I keep running into an error that says "Invalid Procedure Call or Argument". Keep it mind the data contains 4,000,000+ records.
Does anyone know how to fix this VBA issue?
Below is the code
Const pi = 3.14159265358979
Function ACos(ang As Double) As Double
If Abs(ang) <> 1 Then
ACos = pi / 2 - Atn(ang / Sqr(1 - ang * ang))
ElseIf ang = -1 Then
ACos = pi
End If
End Function
Function Radians(ang As Double) As Double
Radians = ang * pi / 180
End Function
Also below is the Make Table Code:
Distance: ACOS(Cos(RADIANS(90-[Latitude]))*Cos(RADIANS(90-[Dlatitude]))+Sin(RADIANS(90-[DLatitude]))*Sin(RADIANS(90-[DLatitude]))*Cos(RADIANS([Longitude]-[DLongitude])))*3858

How to add an error message in a function subroutines on VBA

I am really a freshman to study the VBA. I am confused about how to add an error message in a function subroutines.
Here is my problem, when I finished identify a function, how can I add an error message like this: "Please enter the value in an increasing order"?
e.g: If I type =triangular(3,2,1), where the number is in a decreasing order, I should get an error message.
Here is my code:
Public Function triangular(Minimum As Single, mostlikelyvalue As Single, maximum As Single) As Single
Dim uniform As Single
Dim d As Single
Randomize
Application.Volatile
d = (mostlikelyvalue - Minimum) / (maximum - Minimum)
uniform = Rnd
If uniform <= d Then
triangular = Minimum + (maximum - Minimum) * Sqr(d * uniform)
Else
triangular = Minimum + (maximum - Minimum) * (1 - Sqr((1 - d) * (1 - uniform)))
End If
End Function
You can test for incorrect order, or also an invalid entry directly in your function and return that rather than use error handling
Changed variable names to help avoid errors and confusion with existing function
Use a variant function to hold either the result or one of the two customised error messages
You may as well use Doubles rather than Singles
code
Public Function triangular(dbMinimum As Double, dbMostlikelyvalue As Double, dbMaximum As Double)
Dim uniform As Double
Dim d As Double
Dim dbCnt As Double
dbCnt = dbMinimum * dbMostlikelyvalue * dbMaximum
If dbCnt = 0 Then
triangular = "at least one value is zero"
Exit Function
End If
If dbMostlikelyvalue > dbMaximum Or dbMinimum > dbMostlikelyvalue Then
triangular = "values not sorted"
Exit Function
End If
Randomize
Application.Volatile
d = (dbMostlikelyvalue - dbMinimum) / (dbMaximum - dbMinimum)
uniform = Rnd
If uniform <= d Then
triangular = dbMinimum + (dbMaximum - dbMinimum) * Sqr(d * uniform)
Else
triangular = dbMinimum + (dbMaximum - dbMinimum) * (1 - Sqr((1 - d) * (1 - uniform)))
End If
End Function
Try this
Public Sub Sample
On Error Goto Err
'call your function here
'some more codes here
Exit Sub 'if all goes well code ends here
Err: 'Error handler
MsgBox Err.Description
End Sub

Is it possible to implement Newton's method *once* in VBA?

I need to use Newton's method on closures.
Function f (x as Double, y as Double) as Double
f = x^3-y
End Function
I get the value of y from a cell and then I would like to find out when f is zero. In the toy example above, if the cell contains y=8, then I would expect Newton's method to find a solution close to x=2.
My solution was to make a newton_solve_f function:
Function newton_solve_f (y as Double as Double) as Double
Dim x as Double
x = 0 'initial guess for x
'do Newton's method to find x
...
newton_solve_f = x
End Function
so in effect, I copy paste my code for Newton's method (taken from here) into newton_solve_f.
The problem is that I have several such fs (some with more than two arguments), and it would be really neat if I didn't have to make a separate almost identical newton_solve_f for every one of them.
How would you solve this in VBA?
In Python, for example, it's possible to solve this problem as follows:
def f(y):
def g(x):
return x^3-y
return g
def newton_solve(f):
#do newton's method on f(x)
newton_solve(f(3))
Here f(3) is a function, a closure of one variable. (The closure example on wikipedia is almost identical to this one.)
ps. I know Newton's method also needs the (partial) derivative of f, I'm actually doing something that's more like the secant method, but that's irrelevant for what I'm asking about
Closures are not part of VBA. But you can use static variables within a method scope. They cannot be used outside the method. If you want a variable to visible outside, then you have to use global variable. Preferable declare it public in a module.
We cannot define function inside function in VB. Tried to convert the code given in the link you have mentioned. I hope it helps you. Not well versed with php, but you can see the approach below and make changes accordingly.
Sub Test()
Dim x As Double
Dim y As Double
Dim z As Double
x = Cells(1, 1).Value
y = Cells(1, 2).Value
z = NewtRap("Fun1", "dFun1", x, y)
Cells(1, 3).Value = z
End Sub
Private Function NewtRap(fname As String, dfname As String, x_guess As Double, y_value As Double) As Double
Dim cur_x As Double
Dim Maxiter As Double
Dim Eps As Double
Maxiter = 500
Eps = 0.00001
cur_x = x_guess
For i = 1 To Maxiter
If (fname = "Fun1") Then
fx = Fun1(cur_x)
ElseIf (fname = "dFun1") Then
fx = dFun1(cur_x)
ElseIf (fname = "f") Then
fx = f(cur_x, y_value)
End If
If (dfname = "Fun1") Then
fx = Fun1(cur_x)
ElseIf (dfname = "dFun1") Then
fx = dFun1(cur_x)
ElseIf (dfname = "f") Then
fx = f(cur_x, y_value)
End If
If (Abs(dx) < Eps) Then Exit For
cur_x = cur_x - (fx / dx)
Next i
NewtRap = cur_x
End Function
Function f(x As Double, y As Double) As Double
f = x ^ 3 - y
End Function
Function Fun1(x As Double) As Double
Fun1 = x ^ 2 - 7 * x + 10
End Function
Function dFun1(x As Double) As Double
dFun1 = 2 * x - 7
End Function
So to first summarise: You want to create a function that will find (using Newton-Raphson method) the roots of a function. You already have this written and working for certain functions but would like help expanding your code so it will work with a variety of functions with varying numbers of parameters?
I think you first need to think about what input functions you want it to cover. If you are only dealing with polynomials (as your example suggests), this should be fairly straightforward.
You could have general functions of:
Function fnGeneralCase (x, y, z, w, a1, a2, a3, b1, b2, b3, c1, c2, c3 as Double) as Double
fnGeneralCase = a1*x^3 + a2*x^2 + a3*x + b1*y^3 + b2*y^2 + b3*y + c1*z^3 + c2*z^2 + c3*z + w
End Function
Function fnDerivGeneralCase (x, y, z, w, a1, a2, a3, b1, b2, b3, c1, c2, c3 as Double) as Double
fnDerivGeneralCase = a1*3*x^2 + a2*2*x + a3 + b1*3*y^2 + b2*2*y + b3 + c1*3*z^2 + c2*2*z + c3
End Function
And just set the inputs to zero when you don't need them (which will be for the majority of the time).
So for your example calling:
answer = fnGeneralCase(guess, 0, 0, -8, 1, 0, 0, 0, 0, 0, 0, 0, 0)
basically gives:
function = x^3-8
If you want to include more than polynomials, this will get more complicated but you could still use the above approach...
This seems to be asking 2 related questions:
how to pass a function as an argument in vba.
how to create a closure out of an existing function.
Unfortunately neither of these are really supported, however,
for 1 you can generally work around this by passing a string function name and using 'Application.Run' to invoke the function.
2 is trickier if you have lots of functions with different numbers of parameters, but for a set number of parameters you could add extra parameters to the newton_solve function or maybe use global variables.
e.g.
Public Function f(x as Double, y as Double) as Double
f = x^3-y
End Function
Function newton_solve_f (function_name as String, y as Double) as Double
Dim x as Double
x = 0 'initial guess for x
'do Newton's method to find x
...
' invoke function_name
x = Application.Run(function_name, x, y)
...
newton_solve_f = x
End Function
Assuming f is in a module called 'Module1' you can call this with:
x = newton_solve('Module1.f', 3)
Note that the function you want to call must be public.

Mod with Doubles

Am I doing something wrong or does the VBA Mod operator actually not work with floating point values like Doubles?
So I've always sort of assumed that the VBA Mod operator would work with Doubles based on the VB documentation, but in trying to figure out why my rounding function doesn't work, I found some unexpected Mod behavior.
Here is my code:
Public Function RoundUp(num As Double, Optional nearest As Double = 1)
RoundUp = ((num \ nearest) - ((num Mod nearest) > 0)) * nearest
End Function
RoundUp(12.34) returns 12 instead of 13 so I dug a little deeper and found that:
12.5 Mod 1 returns 0 with the return type of Long, whereas I had expected 0.5 with a type of Double.
Conclusion
As #ckuhn203 points out in his answer, according to the VBA specification,
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result.
And
Usually, the data type of result is a Byte, Byte variant, Integer,
Integer variant, Long, or Variant containing a Long, regardless of
whether or not result is a whole number. Any fractional portion is
truncated.
For my purposes, I need a floating point modulo and so I have decided to use the following:
Public Function FMod(a As Double, b As Double) As Double
FMod = a - Fix(a / b) * b
'http://en.wikipedia.org/wiki/Machine_epsilon
'Unfortunately, this function can only be accurate when `a / b` is outside [-2.22E-16,+2.22E-16]
'Without this correction, FMod(.66, .06) = 5.55111512312578E-17 when it should be 0
If FMod >= -2 ^ -52 And FMod <= 2 ^ -52 Then '+/- 2.22E-16
FMod = 0
End If
End Function
Here are some examples:
FMod(12.5, 1) = 0.5
FMod(5.3, 2) = 1.3
FMod(18.5, 4.2) = 1.7
Using this in my rounding function solves my particular issue.
According to the VB6/VBA documentation
The modulus, or remainder, operator divides number1 by number2
(rounding floating-point numbers to integers) and returns only the
remainder as result. For example, in the following expression, A
(result) equals 5. A = 19 Mod 6.7 Usually, the data type of result is
a Byte, Byte variant, Integer, Integer variant, Long, or Variant
containing a Long, regardless of whether or not result is a whole
number. Any fractional portion is truncated. However, if any
expression is Null, result is Null. Any expression that is Empty is
treated as 0.
Remember, mod returns the remainder of the division. Any integer mod 1 = 0.
debug.print 12 mod 1
'12/1 = 12 r 0
The real culprit here though is that vba truncates (rounds down) the double to an integer before performing the modulo.
?13 mod 10
'==>3
?12.5 mod 10
'==>2
debug.print 12.5 mod 1
'vba truncates 12.5 to 12
debug.print 12 mod 1
'==> 0
I believe that the Mod operator calculates with long type only. The link that you provided is for VB.Net, which is not the same as the VBA you use in MSAccess.
The operator in VBA appears to accept a double type, but simply converts it to a long internally.
This test yielded a result of 1.
9 Mod 4.5
This test yielded a result of 0.
8 Mod 4.5
As a work around your can do some simple math on the values. To get two decimal of precision just multiply the input values by 100 and then divide the result by 100.
result = (123.45*100 Mod 1*100)/100
result = (12345 Mod 100)/100
result = 0.45
I'm late to the party, but just incase this answer is still helpful to someone.
Try This in VBS:
Option Explicit
Call Main()
Sub Main()
WScript.Echo CStr(Is_Rest_Of_Divide_Equal_To_Zero(506.25, 1.5625))
End Sub
Function Is_Rest_Of_Divide_Equal_To_Zero(Divident, Divisor)
Dim Result
Dim DivideResult
If Divident > Divisor Then
DivideResult = Round(Divident/Divisor, 0)
If (DivideResult * Divisor) > Divident Then
Result = False
ElseIf (DivideResult * Divisor) = Divident Then
Result = True
ElseIf (DivideResult * Divisor) < Divident Then
Result = False
End If
ElseIf Divident = Divisor Then
Result = True
ElseIf Divident < Divisor Then
Result = False
End If
Is_Rest_Of_Divide_Equal_To_Zero = Result
End Function
Public Function Modi(d as double) as double
Modi = d - Int(d)
End Function
Dim myDoule as Double
myDoule = 1.99999
Debug.Print Modi(myDoule)
0.99999

Round up to the nearest multiple of a number

This question has already been asked for the C++ language but I need a function for VBA. I tried converting the C++ function to VBA, but it doesn't return the right values.
I need a function that does the following:
RoundUp(23.90, 5)
'return 25
RoundUp(23.90, 10)
'return 30
RoundUp(23.90, 20)
'return 40
RoundUp(23.90, 50)
'return 50
RoundUp(102.50, 5)
'return 105
RoundUp(102.50, 20)
'return 120
Here's what I have so far. It works most of the time, but returns incorrect values for numbers that are less than .5 less than the multiple. So the problem seems to be a rounding problem with how I'm calculating the remainder value.
Public Function RoundUp(dblNumToRound As Double, lMultiple As Long) As Double
Dim rmndr As Long
rmndr = dblNumToRound Mod lMultiple
If rmndr = 0 Then
RoundUp = dblNumToRound
Else
RoundUp = Round(dblNumToRound) + lMultiple - rmndr
End If
End Function
For Example:
RoundUp(49.50, 50)
'Returns 49.50 because rmndr = 0
I'd simply divide by the lMultiple, round up and multiply again.
Assuming you indeed always want to round up (also for negative numbers):
Public Function RoundUp(dblNumToRound As Double, lMultiple As Long) As Double
Dim asDec as Variant
Dim rounded as Variant
asDec = CDec(dblNumToRound)/lMultiple
rounded = Int(asDec)
If rounded <> asDec Then
rounded = rounded + 1
End If
RoundUp = rounded * lMultiple
End Function
I'm not actually a VBA programmer, so this might need a tweaked comma or two. However the important thing is:
Use Decimal (variant subtype) for precision
Let VB do the math for you
Worth trying WorksheetFunction.Ceiling method (Excel)
WorksheetFunction.Ceiling(27.4,5)
Above example will return 30. Here is Link:
https://learn.microsoft.com/en-us/office/vba/api/excel.worksheetfunction.ceiling
A far simpler solution is to add .5 to the number before rounding:
1.1 -> Round(1.1+.5, 0) -> 2