Re: Square Root algorithm
by toma (Vicar) on Jun 25, 2001 at 11:42 UTC
|
The algorithm in your code is using the bisection method,
which has some advantages. It emphasizes reliability
over speed. Common algorithms
for solving nonlinear equations of one variable are
- The bisection method
- Newton's method
- The secant method
There is a nice section on some of this in
Mastering Algorithms with Perl.
Much of the work on numerical methods is published
in fortran, most of which is easy to translate to
perl.
square root function in fortran from
netlib shows a
nice loop for iterating the approximation, which I
translated to perl here:
while ($i++< $iterations)
{
$sqrt = $sqrt + 0.5*($y - $sqrt**2) / $sqrt;
}
It would be interesting to compare this loop to yours.
The netlib code also includes a first guess for the
square root function based on a polynomial approximation.
This approximation only works for numbers within some
range of values, so normalization is used to get within
this range. For example, you might start with the
number 1000, so you divide it by 4 until it is less than
2 but greater than 0.5. When done, you multiply the
square root that you found by
2**(number_of_divides).
The other thing that the netlib code does is to calculate
the number of iterations that are needed, based on
your available machine precision. It also deals with
error conditions, and the case where the normalization
of the number involves making it larger instead of smaller.
I'll leave that as an exercise for the student.
use strict;
my $i=0;
my $iterations=10;
my $y=1e6;
my $divs=0;
while ($y > 2)
{
$y = $y/4;
$divs++;
}
my $sqrt= 0.261599e0 + $y*(1.114292 + $y * (-0.516888 + $y * 0.141067
+));
while ($i++< $iterations)
{
$sqrt = $sqrt + 0.5*($y - $sqrt**2) / $sqrt;
print $sqrt,"\n";
}
print $sqrt*(2**$divs),"\n";
This code converges very quickly, only two iterations
are really needed in this example.
It's a fairly unusual hobby to work on numerical methods,
but you can quickly pick up quite a bit of math while
your at it, and you get to really understand what is
going on in your computer. For instance, some modern processors
have dedicated square root hardware that implements a
square root algorithm directly in silicon.
The downside of numerical methods is that it requires
exquisite attention to detail and testing. People spend
years on this sort of thing, so it is a good idea to
borrow known-good algorithms and code whenever possible.
That's why the code tends to remain in fortran for so
long.
It should work perfectly the first time! - toma | [reply] [d/l] [select] |
Re: Square Root algorithm
by CheeseLord (Deacon) on Jun 25, 2001 at 11:00 UTC
|
Hey, cool problem, nysus... I'll throw out my code, first. Be forewarned, though -- I'm nowhere near the pro you asked for.
#!/usr/bin/perl -w
use strict;
sub sqroot {
my $maxdiff = 1e-18;
my ($value, $num) = (1, shift @_);
my $i = ''; # Added
$num *= -1 and $i = 'i' if ($num < 0); # Added
my $oldvalue = 0;
while (abs($oldvalue - $value) > $maxdiff) {
$oldvalue = $value;
$value = $num / $value;
$value = ($value + $oldvalue) / 2;
}
# return $value; # (old)
return $value . $i;
}
my $num;
do {
print "Please enter a number: "; # changed from "positive number"
chomp ($num = <STDIN>);
# } until ($num =~ /^\d*[.\d]\d*$/); # (old regex)
} until ($num =~ /^-?\d+\.?\d*$/);
my $sqrt = sqroot $num;
print "Square root of $num: $sqrt\n";
Now, for a few comments:
- IMO, your code was broken down perhaps a little too much. Of course, depending on what you'll want to do in the future, you may want to do that. But I'd suggest a name such as diff instead of get_accuracy, for two reasons: it is shorter (which I know can be a bad thing in some cases), and it's more descriptive of its function. You're only returning the difference between the two numbers, and I'd prefer something that I'd be able to easily recognize later on, so I could steal it and put it in another program.
- Also, I moved the print statement out of the subs... once again, it's only my opinion, but I think those things should be outside of the sub (If I call "sqroot", I don't expect it to print anything out. "print_sqrt" on the other hand...)
- You'll notice I also have a different regex and a do-until loop... the loop style is just personal preference, but I feel the regex is better, as it allows the user to enter in just the number (e.g. "10" vs. "10.0"), however, it may not be the most efficent. I'd like a little feedback from those who know more than I on that, actually...
Once again, considering that optimial is a relative term, you're free to call my ideas ridiculous, if you like. :) Once again, though, cool problem... made me think a good bit. (I'll probably still be thinking about it tomorrow...)
Update: I just couldn't leave well enough alone. Above are modifications to the original script that allow processing of negative numbers, as well as an improved (?) regex. Also changed $maxdiff as per ariels' suggestion.
His Royal Cheeziness | [reply] [d/l] [select] |
|
|
| [reply] |
Re: Square Root algorithm
by jepri (Parson) on Jun 25, 2001 at 11:19 UTC
|
Beatnik he's trying to do it without useing the built in square root function. Well, first up your regex fails on an integer.
Update: Well there ya go, I should have paid attention in class. Go read ariels tract on finding roots, he's clearly done this in a lot more detail than I have.
And I usually shy away from ariels notation, because to me 1e-18 says e^-18 rather than 10^-18. That always bugged me. Personally I'd rather use 10**(-18).
/Update
Since the way to find roots with maximum speed is to use hand-tuned assembler that exploits the quirks of the processor, there's no need to stress speed.
As for your concern about calling new_guess twice, why not try this?
my $n=0;
Hahaha:
$guess = new_guess($guess, $x); # MAKE OLD GUESS THE NEW GUESS AND RUN
+ WHILE LOOP AGAIN
$n=new_guess($guess, $x);
goto Hahaha if get_accuracy($guess, $n) > .000000000000000001 { # CHE
+CK DIFFERENCE BETWEEN OLD GUESS AND NEW GUESS
Actually this should work fine:
my ($guess, $x) = @_;
my $n=0;
while ( get_accuracy($guess, $n) > .000000000000000001) { # C
+HECK DIFFERENCE BETWEEN OLD GUESS AND NEW GUESS
$guess = $n;
$n=new_guess($guess, $x); # MAKE OLD GUESS THE NEW GUESS AND RUN WHI
+LE LOOP AGAIN
}
____________________
Jeremy
I didn't believe in evil until I dated it. | [reply] [d/l] [select] |
|
|
| [reply] |
|
|
I remeber for One funny alogorith for fined square root from some numbers :-)
add together odd-numbers from 1 while total sum is greater or equal to input number :-). Count of numbers is root :-))
exam.
root from 9 : 1+3+5=9 number 3
root from 25 : 1+3+5+7+9=25 number 5
root from 100: 1+3+5+7+9+11+13+15+17+19=100 number 10 :-)
easy and funny but works :-) It's usefull for locating starting number for iteration process.
This algorithm is very fast because use only adding and testing :-) but today it's irelevant becuase processor are very fast and using math-coprocessor :-).
several years ago thas was easy to creat it for lots of ASM instructions :-))
| [reply] |
|
|
|
|
Re: Square Root algorithm
by tachyon (Chancellor) on Jun 25, 2001 at 16:32 UTC
|
Here's mine, did someone say GOLF!
cheers
tachyon
print sqroot(2,100);
# long version
sub sqroot {
$number = shift;
$iterate = shift;
$guess = $number;
for (0..$iterate) {
$guess = (($number/$guess) + $guess)/2 ;
}
return $guess;
}
# golf at 44 strokes
sub sqroot {
($n,$i)=@_;$g=$n;$g=($n/$g+$g)/2for 0..$i;$g
}
| [reply] [d/l] |
|
|
sub sqroot {
#23456789_123456789_123456789_123456
$g++;$g=($_[0]/$g+$g)/2for-1..pop;$g
}
At 29 characters. It seems to work correctly on perl5.6
jynx
update: d'oh, i was erroneously fooled by my browser when entering the snippet, it's actually 36 characters... | [reply] [d/l] |
Re: Square Root algorithm
by cLive ;-) (Prior) on Jun 25, 2001 at 11:21 UTC
|
#!/usr/bin/perl
use strict;
my ($guess,$num);
# get number
while(<STDIN>) {
$guess = $num = $_+0; last if ($num>0);
}
# iterate until within 1e-13
# go further and you need some math module, I think :)
while (abs($num - $guess**2) > 1e-13) {
$guess = (($num/$guess) + $guess)/2;
}
# print our guess and what Perl says
print "My guess of square root of $num is $guess\n";
print "Perl says it's ".$num**.5."\n";
cLive ;-)
PS - golf version? :)
#!/usr/bin/perl
use strict;
my $n;
while(<STDIN>) { $n = $_+0; last if $n>0}
while (abs($n - $_**2) > 1e-13){ $_ = ($n/$_ + $_)/2}
print "Guess: $_\nperl: ".$n**.5."\n";
| [reply] [d/l] [select] |
|
|
| [reply] |
|
|
while (abs($num - $guess**2) > 1e-13) {
to the more accurate comparison (with 2 more decimal places of accuracy):
while (abs($num/$guess - $guess) > 1e-15) {
and it works fine :)
cLive ;-) | [reply] [d/l] [select] |
Re: Square Root algorithm
by Beatnik (Parson) on Jun 25, 2001 at 10:43 UTC
|
I could be misinterpreting the Square Root line here, but this does the trick for me...
$a=25;
$b=$a**(1/2); # 25^0.5
print $b; # 5
or with 2
$a=2;
$b=$a**(1/2); #2^0.5
print $b; #1.4142135623731
Greetz
Beatnik
... Quidquid perl dictum sit, altum viditur. | [reply] [d/l] [select] |
Re: Square Root algorithm
by John M. Dlugosz (Monsignor) on Jun 29, 2001 at 18:56 UTC
|
I've done this, really. In days long past, I was doing Lambert Shading on a 16-bit machine with integer arithmetic. In assembly language, I coded a square root using Newton's Method. You use a simple average; Newton uses the slope (based on the derivitive function) to project to the next guess very accuratly.
The interesting thing is that in my shading, iterating over a row of pixels, the result is probably very close to the result of the previous pixel, since the whole point of this is "smooth shading", right? So I used the previous call as the initial guess, and most often got the right answer after one iteration.
—John | [reply] |