I am making a program that deals with basic algebraic functions. The following function is meant to calculate the value of x given a certain value of y. The problem is that the answer can't be exact, so I need to figure out how to find the closest number. I thought rounding would work, but it doesn't.

Code:

Private Sub cmdX_Click()
Dim math As New clsMathParser
Dim x As Single
If lstEqu.Text = vbNullString Then
MsgBox "You need to select an equation from the list.", vbCritical
Exit Sub
End If
math.StoreExpression lstEqu.Text
For x = xmin To xmax Step 0.001
If Round(math.Eval1(x), 3) = Val(txtY) Then
txtX = x
Exit For
End If
Next x
End Sub

Seems like an odd way to go about it? Normally if you know the value of Y then you would plug Y into your equation and the result would be the value of X. It seems that stepping up the value of x in a loop and testing to see if it is equal to Y is just guessing.

How do you know that the range of X values will give a result which will satisfy the equation for a given value of Y ?

eg take the equation Y = (X / 2) -10
If you wanted to find X for Y = 20 and your X range was 0 to 10 you'd never get a result.

You could, instead of rounding, check if the calculated value of Y was within a given tolerance of the required Y

eq

Code:

Dim math As New clsMathParser
Dim x As Single
Dim y As Single
For x = xmin To xmax Step 0.001
y = math.Eval1(x)
If y => CSng(txtY.Text) - 0.0005 And y =< CSng(txtY.Text) + 0.0005 Then
txtX.Text = CStr(x)
Exit For
End If
Next x

in the above you will get a result if the calculcated value of y is inclusively within + or - 0.0005 of the value desired.

Last edited by Doogle; Feb 24th, 2012 at 12:29 AM.
Reason: Changed the example equation

Seems like an odd way to go about it? Normally if you know the value of Y then you would plug Y into your equation and the result would be the value of X.

Yeah that's how a person might do it, but to program it that way would be very hard because instead of having the x value which is easy to convert the right side of the equation to polish notation and then evaluate, you have the y value so it's seems easier to just brute force it. (Unless maybe im missing something?)

How do you know that the range of X values will give a result which will satisfy the equation for a given value of Y ?

I don't, but it's a graphing utility so the user is meant to enter the xmin and xmax correctly lol.

Whenever I enter zero for y, I get -10 using your example with any equation and I can't figure out why.

I got it to work. I forgot to add the line math.StoreExpression and I also had to reduce the accuracy to 2 decimal places because it was giving me one off answers fairly often with three and I have no idea why.

Code:

math.StoreExpression lstEqu.Text
For X = xmin To xmax Step 0.01
Y = math.Eval1(X)
If Y >= CSng(txtY.Text) - 0.005 And Y <= CSng(txtY.Text) + 0.005 Then
txtX.Text = CStr(Round(X, 2))
Exit For
End If
Next X

It still doesn't work how I want. Gives me 0.001 off and if I just add 0.001 most of the time it works, but when checking higher numbers it gives 0.001 too much. I really need this to work. I am going to attach the whole program so someone can test it. The get X button is the problem. To test it add a liner equation (ex 4x-6) and then press get x and it will give you 1.499 instead of 1.5.

I might even consider paying someone a small amount through paypal if they can figure this out.

Your fundamental problem is that you are rounding to the same number of significant figures that you are performing your computations with. However, since rounding takes place at one significant figure higher than that, this means your displayed solution will be impacted by rounding error. To get around that, you merely need to either round to one digit less, or operate at one significant digit more. I assume you can't reduce your displayed digit, so that makes the decision easy:

Code:

txtX = vbNullString
math.StoreExpression lstEqu.Text
For X = xmin To xmax Step 0.0001
Y = math.Eval1(X)
If Y >= CSng(txtY) - 0.0005 And Y <= CSng(txtY) + 0.0005 Then
txtX = CStr(Round(X, 3))
Exit For
End If
Next X

Unfortunately, this adds 10 times as many computations. There are a few ways to address this. Notably, you can clean up the code a little pit by minimizing the processing that take place inside the For loop:

Code:

Dim minY As Single
Dim maxY As Single
minY = CSng(txtY) - 0.0005
maxY = minY + 0.001
txtX = vbNullString
math.StoreExpression lstEqu.Text
For X = xmin To xmax Step 0.0001
Y = math.Eval1(X)
If Y >= minY And Y <= maxY Then
txtX = CStr(Round(X, 3))
Exit For
End If
Next X

But if you really want to make it run faster you need to redesign your approach entirely to use things like binary searches or simulated annealing, and you may need to customize this stuff for non-linear functions.

It doesn't work if I remove the exit for (which I would need to for problems with multiple answers). It gives me the same answer twice.

Code:

Dim minY As Single
Dim maxY As Single
minY = CSng(txtY) - 0.0005
maxY = minY + 0.001
'txtX = vbNullString
txtInfo = vbNullString
math.StoreExpression lstEqu.Text
For x = xmin To xmax Step 0.0001
y = math.Eval1(x)
If y >= minY And y <= maxY Then
'txtX = CStr(Round(x, 3))
txtInfo = txtInfo & Round(y, 4) & vbTab & Round(x, 3) & vbCrLf
End If
Next x

Any ideas? Would I need to change to a binary search to make this work?

Of course it gives you the same answer twice. That is a direct consequence of your algorithm. The most obvious way to get around this is to compare rounded results and if they are equal to one another, discard the second result and keep searching. However, you're going to have to figure out how to handle a bevy of problems. For example, take the following quadrilateral equations in which you want to solve for x when y = 0:

y = x^2 + 1
If y = 0, x = i (the imaginary unit). Thus, there are no real solutions (give that your algorithm is only searching for roots between -10 and +10, this likely isn't a big deal).

y = x^2 + 2x + 1 = (x + 1)^2
Both roots of this quadrilateral are -1. Thus, if you discard redundant results as suggested above, you're going to skip over a root (depending on what you are actually trying to do, this also might not be a problem).

y = x^2 - 3x + 2.2499999999 = (x - 1.49999)(x - 1.50001)
In this case there are two separate roots, x = 1.49999 and x = 1.50001. However, the difference between the two roots (0.00002) has more significant figures than your algorithm works with, and thus the differences between them will be rounded and your algorithm will simply return 1.5 for the first root and discard remaining results.

Frankly, I think it's a bit of a fool's errand to try to program for all possibilities, even on something as seemingly straightforward as this. I suggest you simply make a design decision as to what functionality is important and accept that you will not be able to produce perfect results for all situations.

I found someone who was working on a similar program and I emailed him and asked how he did it. He said that he just wrote an algorithm that keeps dividing by two and checking if greater or less than to determine the answer. This seems a lot simpler than this and I think it will give me better results. I think it may take more time to compute though. Do you think I should use this approach?

Don't know how I'd make it work with multiple answers though.

Last edited by veebee123; Mar 10th, 2012 at 03:16 PM.

the process for solving your equation is called the newton-raphson method

you simple enter any x value into the derivative of you target equation and feed the output back into the equation untill the input andout put are the same!

there is a problem with NRM in that it can perform some perfect-oscilations and so not resolve the equation..

for that reason it is worth while capturing the last pair of outputs to ensure you have not started oscilating.

There is a paper on this problem, I wrote, that is now taught at OU

you simple enter any x value into the derivative of you target equation and feed the output back into the equation untill the input andout put are the same!

Thanks for the help, but if I use this method, then I also have to write code to find the derivative first.

Anyone know if I could make the divide by 2 method work with multiple answers?