Page 1 of 1
UDF - Sqrt - Math comparison doesn't work
Posted: 01 Jul 2015 17:03
by Marco
Code: Select all
function sqrt($num) {
// Returns the square root of a positive number in base 10.
// Any other argument causes the function to throw an error.
// The result has 10E-13 precision.
// Based on the Babylonian algorithm (see https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method).
//
// $num the positive number in base 10 to calculate the square root of
assert regexmatches("$num", "[^0-9.,]") == "", "Argument is not a positive number";
$num = eval($num);
$eps = eval(1/1000000000000);
if ($num >= 100) {
$root = $num/20;
} elseif ($num >= 2) {
$root = $num/2;
} elseif ($num >= 2/100) {
$root = $num;
} else {
$root = $num*10;
};
while (abs($root^2 - $num) > $eps) {
$root = ($root + $num/$root)/2;
};
return $root;
}
If you try step-by-step to compute sqrt(5.2) you'll get stuck at a residual of 0.0000000000000195399252334028. And no matter how much I raise $eps, the loop will never terminate. What am I doing wrong?
Re: UDF - Sqrt - Math comparison doesn't work
Posted: 01 Jul 2015 17:30
by bdeshi
I'd say it's because XY isn't known for its mathematical excellence.
try with this "debug modification"
Code: Select all
step;
$acc = abs($root^2 - $num);
while ($acc > $eps) {
$root = ($root + $num/$root)/2;
$acc = abs($root^2 - $num);
};
ed. hint: after a few steps, $acc gets converted to scientific notation, and as long as this is in the form
nE-x, XY only takes the -n part for comparison (probably thinking it's salvaging a string)
Re: UDF - Sqrt - Math comparison doesn't work
Posted: 01 Jul 2015 17:34
by highend
Just break your loop if you've reached the max convergence.
Code: Select all
while (abs($root^2 - $num) > $eps) {
$root = ($root + $num/$root)/2;
if ($root == $last) { return $root; }
$last = $root;
}
Re: UDF - Sqrt - Math comparison doesn't work
Posted: 01 Jul 2015 23:52
by Marco
@Sammy:

I didn't know XY was capable of E notation - I couldn't even possibly think of that.
@highend: yep, I thought about that solution, but didn't felt elegant to me. But you gave me some inspiration...
Code: Select all
while ($root != $root_prev) {
$root_prev = $root;
$root = ($root + $num/$root)/2;
};
return $root;