Click to See Complete Forum and Search --> : tough VB question - see if you can answer it!


January 28th, 2000, 04:16 PM
It took me a while to get it, and lots of frustation, but I eventually got it... Here is the question:

I want any one of you to write code that will solve the equation a ^ x = b for x, if the values of a and b are given. The hard part to it? You can't use any LOG () or EXP () functions. For that matter, the only mathematical function you can use is addition/subtraction, multiplication/division, and ^.

So far nobody I asked in my neighborhood has gotten it...Usually is is just:


public Sub Exponent (a as double, b as double)
x = log (b) / log (a)
End sub




I want you to do it without log () functions or exp () functions... Or any built in VB math function besides +/-, *//, ^.

xxMariusxx
January 28th, 2000, 08:59 PM
Taking the inverse of a^x = b we come up with x = Log_a(b). This can be rewritten as ln(b)/ln(a)...so now all we need are natural logarithms.

Assuming f(x)=1/x, then the antiderivative F(x)=ln(x) over the interval 1 to x. We can approximate this integral using Reimann Sums...so all we're really looking for are the definite integrals of 1/x from 1 to a and 1/x from 1 to b. Once we get those we just divide and there's our answer. Not 100%, but we can get pretty darn close.


public Sub solveForX(a as Double, b as Double)
Dim answer as string
Dim i as Long
Dim reimannSection as Double
Dim naturalLogOfA as Double
Dim naturalLogOfB as Double

' catch division by zero
If a = 1 then
answer = "The answer is: Infinity"
Exit Sub
End If

' initialize variables
naturalLogOfA = 0
naturalLogOfB = 0

' integrate on interval a
reimannSection = (a - 1) / 1000000
for i = 1 to 1000000
naturalLogOfA = naturalLogOfA + (reimannSection * (1 / (1 + i * reimannSection)))
next i
' integrate on interval b
reimannSection = (b - 1) / 1000000
for i = 1 to 1000000
naturalLogOfB = naturalLogOfB + (reimannSection * (1 / (1 + i * reimannSection)))
next i

' calculate answer
answer = "The answer is: " & CStr(naturalLogOfB / naturalLogOfA)
End Sub




We can, of course, write in code to round to the nearest significant digit.

January 29th, 2000, 03:37 PM
it doesn't appear to work...i can't get an answer. I entered the code:

Msgbox answer



after you say what the answer is, yet I get no msgbox response....

AndyK
January 29th, 2000, 04:21 PM
yes place MsgBox answer right after
answer = "The answer is: " & CStr(naturalLogOfB / naturalLogOfA)
so it will look like this
answer = "The answer is: " & CStr(naturalLogOfB / naturalLogOfA)
MsgBox answer



But to tell you the truth ...this is great but really "slick" piece of code....my PC calculates this for like 2 secs...too long, and I entered really basic numbers: a = 5 and b = 125 where x should be 3....I get the answer: 2.99996476858295 but other then that it's great code.

AndyK
January 29th, 2000, 04:30 PM
I got better solution for this...not sure thos if it'll satisfy you, but it's pretty darn good :)
make text1, text2,text3 and text4 and command button
assume text1 is a, text2 is x (unknown) and text3 is b
SET all textboxes to "0" except text2, because text2 is our "x" (unknown), so might as well set it to "x"

private Sub Command1_Click()
num = Text3.Text / Text1.Text
Do Until num >= Text1.Text
num = num / Text1.Text
Text4.Text = Text4.Text + 1
Loop
MsgBox Text4
End Sub



Ok now enter 5 into text1 (now a = 5), put 125 into text3 (now b = 125), press command1.....the asnwer should pop-up in msgbox, answer also is stored in text4...it's 3
I tried many integers and I lways get correct answer, so that's alternative way.....unless i didn't understand something from your problem

AndyK
January 29th, 2000, 04:32 PM
forgot...don't forget to clear text4 everytime you click command1, or existed number will be added to a new one...giving you wrong answer

AndyK
January 29th, 2000, 04:41 PM
crap....not clear text4, set it to "2" because before loop you divide 125 by 5, making it as 1st division, and when you get to 5 you get second 1st becouse 5 = 5^1,so text4 should always be 2 before pressing command1.....It's hard to explain the way I think, just look through code and you will get it...hopefully.
What it does is, take b and keeps on dividing it by a until the answer will be equal to a, with each division it adds 1 to text4 as if what power.....confusing ehh?

AndyK
January 29th, 2000, 04:50 PM
ok sorry about so many replys....here is the final code:
1. Make text1,(optionaly text2), text3,text4, and command1
2. Place this code into form

private Sub Command1_Click()
Text4 = "2"
num = Text3.Text / Text1.Text
Do Until num = Text1.Text
num = num / Text1.Text
Text4.Text = Text4.Text + 1
Loop
MsgBox Text4
End Sub

private Sub Form_Load()
Text1 = "5"
Text3 = "125"
Text4 = "2"
Text4.Visible = false
End Sub





Note: It works only with real numbers :(

xxMariusxx
January 30th, 2000, 12:16 AM
Yeah, it takes a while 'cause it's approximating with Reimann Sums...the idea is to take the limit as the number of sections goes to infinity...or rather as the value of reimannSection approaches zero. Since we realistically can't do this, I picked 1,000,000 cycles for the loop just 'cause in my mind it's a pretty big number. However, doing it this way, the larger numbers you use the greater the error....much larger magnitude of error dividing 700000 into a million sections than if you did it with just 7.

Try taking the loop down to 100,000 (and sections down to 1/100,000) and it should calculate instantly. The margin or error will be greater, but then it depends on how many significant digits you need. If you only want a few places after the decimal, this should suffice...you can round up to the nearest significant digit.

I believe there's a Taylor Series that can do this integral too...and then you just calculate the error and factor it in. But, alas, it's been a while since I had calculus...I really don't remember this stuff anymore.

xxMariusxx

Dr. Inferno
January 30th, 2000, 01:56 PM
I did something totally different. The problem with Andy K's code is that if you entered the equation:

3 ^ x = 5

It wouldn't be able to solve it...all it works with is if x is an integer. In many cases, it is not. The code I got was something like this. You have text1 and text3, so the equation would be this:

val(text1) ^ x = val(text3), where it solves for x. The answer is recorded in xres$, and put in a textbox text4.



xres$ = " "
'Assign variables y , n2 , n1 , and f
Y = 1 'Y is equal to 1. It is the exponent being tested
n2 = Val(Text3.Text) 'The answer of the equation. In 3^x=5, it would be 5.
n1 = Val(Text1.Text) 'The number. In 3^x=5, it would be 3.
'F is how many times the process shall be repeated. F is 0 in the start.
If n2 = 0 then Text4.Text = "": Exit Sub 'Make sure it is not a ^ x = 0, nonreal answer
If n1 = 1 And n2 <> 1 then Text4.Text = "": Exit Sub 'Make sure it is not 1 ^ x = 2, can't have infinity!
If n2 < 0 And n1 > 0 then Text4.Text = "": Exit Sub 'Make sure it is not a positive to the power of x is negative - can't have negative infinity!
f = 0
go:
f = f + 1 'Add 1 to f to indicate process has been repeated one more time
a1 = n1 ^ Y 'First number, is n1 ^ y, where y is the exponent, and n1 is val(text1.text)
a2 = n1 ^ (Y + 1) 'Second number. N1 ^ (Y+1)
If a1 = n2 then xres$ = Str(Y): GoTo Done 'If x is a whole number, ends.
If n2 = 1 And n1 <> 0 then xres$ = Str(0): GoTo Done 'If n1^y = 1, then ends. Answer is 0
If a2 = n2 then xres$ = Str(Y + 1): GoTo Done 'If n1 ^ (y + 1) = n2, then finish
If f = 1000 then GoTo Done
If a1 < n2 And n2 < a2 then 'If n1 ^ y is less than n2, but n1 ^ (y+1) is greater than n2, then add to final answer. In 3^x = 5, if 3^1 < 5 and 3^2 > 5, then 1 is added to the answer.
xres$ = xres$ + Str(Y) ' Add to answer
getval n1 ^ Y, n2 'get new number for testing
n2 = trueanswer 'N2 is that new number
GoTo go 'Restart process
End If
If a2 < n2 then Y = Y + 1: GoTo go 'If n1 ^ (y+1) < n2 then add 1 to y: restart process. In 3^x = 5, if 3^1 < 5, then add 1 to 1 and restart process
If a1 > n2 then Y = Y - 1: GoTo go 'If n1 ^ y > n2 then subtract 1 from y: restart. In 3^x = 5, if 3 ^ 2 > 5, then subtract 1 from 2 and restart process
Done:
Text4.Text = " "
xres$ = Left(xres$, 32) 'get 16 digits of xres$ - no exponential notation here!
d = len(xres$) - 1 'get length minus 1
If d = 1 then GoTo AlmostDone 'If there is only 1 digit, skip this process.
xres$ = Val(xres$) / (10 ^ d) 'Divide xres$ by 10 ^ d. If it was 146514331, then now it is 1.46514331
AlmostDone:
Text4.Text = Val(xres$) 'Show xres$ briefly to the user
xres$ = Val(Text4.Text) 'Update xres$ to the value of Text4.text, which displays 16 characters of xres$
finished:
If Val(Text1.Text) ^ 10 < Val(Text3.Text) then xres$ = Val(xres$) * 10 'If n1 ^ 10 < n2, then multiply xres$ * 10. If 3 ^ 10 < 60,000, then multiply the answer by 10
If Val(Text1.Text) ^ 100 < Val(Text3.Text) then xres$ = Val(xres$) * 100 'Same thing, but with 100
Text4.Text = xres$ 'Show final answer to user
End Sub





Then you need one more sub:



public Sub getval(Number1, Number2)
answer = Number2 / Number1
trueanswer = answer ^ 10 'Getting new number. Formula is (n2 / n1) ^ 10
End Sub

AndyK
January 30th, 2000, 01:58 PM
So was my solution valid for real numbers or is it totally off topic? Logically it should be valid but I'm not sure.

xxMariusxx
January 31st, 2000, 02:33 AM
Wow, that was a pretty neat way of looking at it!

Your method is much more accurate than mine was, but I found a small problem. The way you got rid of the spaces with the Val function didn't work with your method of calculating the location of the decimal place...it ended up with the right answer but off by about -17 powers of 10 (for 3^x = 5). So this needed to be fixed. Also, this aside, playing with powers of two (being a programmer and all this was easiest for me to think with) I decided to see how high I could get and how much the accuracy degraded with larger numbers. For the most part it stayed much more accurate than my own method, going down to the thousandth digit. However, the last two lines you put in there weren't sufficient to error trap for power differences over 10^2 (this wasn't seen if the answer was a whole number..only decimals. 2^x=1024 worked but 2^x=1025 didn't)...I'd get the correct answer but it'd end up being off by several powers of 10.

So what I did was pull out the spaces in the string with a Replace(xres, " ", ""). Then I started at the left of the string and used a DO WHILE LOOP to move the decimal place through the loop until a^x became larger than b.

Some other things...good commenting. Try to make a habit of avoiding GoTo statements and make sure to Dim your variables (it's generally a good idea to use Option Explicit in your general declarations section)...this avoids creating spaghetti code. A little of the code was redundant (the getval sub required answer to be global and the result could've been calculated in the main procedure as a more of a literal) but the premise was sound and I thought extremely innovative.

I rewrote your code to fix the bugs and make it a little easier to follow. Hope you don't mind. All in all I'd say you had a pretty **** good solution there!!!


public Sub SolveForX()
Dim xres as string
Dim testExp as Double
Dim count as Long
Dim foundAnswer as Boolean
Dim n1 as Double
Dim n2 as Double
Dim a1 as Double
Dim a2 as Double

' initialize variables
xres = " "
testExp = 1
n1 = Val(Text1.Text)
n2 = Val(Text3.Text)
foundAnswer = false
count = 0

' take care of special cases
If n2 = 0 then
Text4.Text = "non-real answer"
Exit Sub
ElseIf n1 = 1 And n2 <> 1 then
Text4.Text = "non-real answer"
Exit Sub
ElseIf n2 < 0 And n1 > 0 then
Text4.Text = "non-real answer"
Exit Sub
ElseIf n2 = 1 And n1 <> 0 then
Text4.Text = "0"
ElseIf n1 = 0 then
Text4.Text = "Infinity"
Exit Sub
End If

' determine string containing the numbers contained in our answer
' delimited by spaces
Do While foundAnswer = false
count = count + 1
a1 = n1 ^ testExp
a2 = n1 ^ (testExp + 1)
If a1 = n2 then
xres = Str(testExp)
foundAnswer = true
ElseIf a2 = n2 then
xres = Str(testExp + 1)
foundAnswer = true
ElseIf count = 1000 then
foundAnswer = true
ElseIf a1 < n2 And n2 < a2 then
xres = xres + Str(testExp)
n2 = (n2 / n1 ^ testExp) ^ 10
ElseIf a2 < n2 then
testExp = testExp + 1
ElseIf a1 > n2 then
testExp = testExp - 1
End If
Loop

' take the spaces out of our string and shorten to 16 characters
xres = Left(xres, 32)
xres = Replace(xres, " ", "")

' determine where to put the decimal place
count = 1
Do While (CDbl(Text1.Text) ^ CDbl(Left(xres, count))) < CDbl(Text3.Text)
count = count + 1
Loop
count = count - 1
xres = Left(xres, count) & "." & Right(xres, len(xres) - count)

' display final answer
Text4.Text = xres
End Sub




xxMariusxx