Download or view root.frink in plain text format
// Functions for taking roots of a number using Newton's method to arbitrary
// precision using the root[] function.
//
// This function also performs integer and rational exponents to arbitrary
// precision using its pow[] function.
//
// These methods shouldn't be used if you're already working with imprecise
// floating-point numbers.
//
// Newton's method converges quadratically if you have a good guess.
// It can be horribly slow if you don't start with a good guess.
//
// This uses a few tricks:
// * Uses floating-point math (when possible) for a good initial guess.
// * Dynamically increases the working precision as estimates get better.
// Convenience function to do square root to current working precision.
sqrt[n] := sqrt[n, getPrecision[]]
// Convenience method for sqrt.
sqrt[n, digits, debug=false] := root[n, 2, digits, debug]
// Convenience function to do power to current working precision.
pow[n, p] := pow[n, p, getPrecision[]]
// Arbitrary-precision power
pow[n, p, digits, debug=false] :=
{
if debug
println["Entering pow $n $p $digits"]
if p == 0
return 1
else
{
origPrec = getPrecision[]
try
{
setPrecision[digits+4]
return root[n, 1/p, digits, debug]
}
finally
setPrecision[origPrec]
}
}
// Convenience function to do root to current working precision.
root[n, p] := root[n, p, getPrecision[]]
// General arbitrary-precision root finder
// n is the number, p is the root (e.g. 2 for square root, 3 for cube root)
root[n, p, digits, debug=false] :=
{
if debug
println["in root[$n, $p]"]
if p == 1
return n
if p == 0
return 1
if p < 0
{
origPrec = getPrecision[]
try
{
setPrecision[digits+4]
return 1/root[n, -p]
}
finally
setPrecision[origPrec]
}
alter = false
if n<0
{
if p == 2
{
alter=true
factor = i
n = negate[n]
} else
if p mod 2 == 1 // Negative base, odd power
{
alter = true
factor = -1
n = negate[n] // Negative base, even power
} else
{
println["Arbitrary-precision root cannot produce complex numbers. Arguments were root[$n, $p, $digits]"]
return undef
}
}
origPrec = getPrecision[]
try
{
// Handle exact rational numbers
if isRational[p]
{
prec = getPrecision[]
setPrecision[digits+3]
// TODO: This needs to use arbitary-precision powers!
// We can't apparently replace it with call to pow[] because
// the stack never terminates.
retval = root[n, numerator[p], digits, debug]^denominator[p]
if alter
retval = retval * factor
setPrecision[prec]
return retval
}
prec = getPrecision[]
err = 10.^-(ceil[digits]+1)
// Initial guess
setPrecision[5]
if (n<1e+308 and n>1e-323)
{
x = n^(1/p) // Use floating-point if we can for guess
err = err * x / 10.
scale = approxLog2[x] / 3.219 // Approx. log 10
} else
{
x = 2.^(approxLog2[n]/p) // Dumb guess; could use introot function below
err = err * x
scale = approxLog2[x] / 3.219 // Approx. log 10
}
if scale == 0
scale = 1
if (debug)
{
println["First guess: $x"]
println["Err is: $err"]
println["Scale is: $scale"]
}
newWorkingPrecision = ceil[min[30,digits+3]]
if newWorkingPrecision < 30
newWorkingPrecision = 30
workingPrecision = 15
diff = abs[x]
scaledErr = abs[err*scale]
while (workingPrecision < digits+p) || (abs[diff] > scaledErr)
{
workingPrecision = newWorkingPrecision
if debug
println["precision is $workingPrecision"]
setPrecision[workingPrecision]
oldx = x
if p==2
{
x = (x + n / x) / 2
diff = oldx - x
} else
{
// TODO: This needs to use arbitary-precision powers!
// We can't apparently replace it with call to pow[] because
// the stack never terminates.
errterm = (x^p - n)/ (p x^(p-1))
x = x - errterm
diff = errterm
}
if debug
{
println["x is $x"]
println["diff is $diff"]
}
// This slightly more than doubles working digits.
setPrecision[10]
if diff == 0
goodDigits = workingPrecision * 2
else
{
if debug
println["approxLog2 is " + approxLog2[abs[diff]]]
goodDigits = -approxLog2[abs[diff]]/3.219+scale
}
if debug
println["Good digits: $goodDigits"]
if (goodDigits < 30)
goodDigits = 30
newWorkingPrecision = min[ceil[digits+p+1], ceil[goodDigits*2.1]]
}
if (debug)
println["Final diff is $diff"]
if alter
{
setPrecision[digits+p+1]
retval = factor * x
} else
retval = x
return retval
}
finally
setPrecision[origPrec]
}
// Inverse square root. 1/sqrt[n].
inverseSquareRoot[n, digits, debug=false] :=
{
alter = false
if n<0
{
alter=true
factor = i
n = -n
}
origPrec = getPrecision[]
try
{
prec = getPrecision[]
err = 10.^-(ceil[digits]+1)
// Initial guess
setPrecision[5]
if (n<1e+308 and n>1e-323)
{
x = 1/(n^(1/2)) // Use floating-point if we can for guess
err = err * x / 10.
scale = approxLog2[x] / 3.219 // Approx. log 10
} else
{
x = 1/(2.^(approxLog2[n]/2))// Dumb guess; could use introot function below
err = err * x
scale = approxLog2[x] / 3.219 // Approx. log 10
}
if scale == 0
scale = 1
if (debug)
{
println["First guess: $x"]
println["Err is: $err"]
println["Scale is: $scale"]
}
newWorkingPrecision = ceil[min[30,digits+3]]
if newWorkingPrecision < 30
newWorkingPrecision = 30
workingPrecision = 15
diff = abs[x]
scaledErr = abs[err*scale]
while (workingPrecision < digits+2) || (diff > scaledErr)
{
workingPrecision = newWorkingPrecision
if debug
println["precision is $workingPrecision"]
setPrecision[workingPrecision]
oldx = x
diff = 1 - n * x^2
x = x + diff * x/2
if debug
{
println["x is $x"]
println["diff is $diff"]
}
// This slightly more than doubles working digits.
setPrecision[10]
if diff == 0
goodDigits = workingPrecision * 2
else
goodDigits = -approxLog2[abs[diff]]/3.219+scale
if debug
println["Good digits: $goodDigits"]
if (goodDigits < 30)
goodDigits = 30
newWorkingPrecision = min[ceil[digits+3], ceil[goodDigits*1.8]]
}
if (debug)
println["Final diff is $diff"]
if alter
{
setPrecision[digits+2+1]
retval = factor * x
} else
retval = x
return retval
}
finally
setPrecision[origPrec]
}
// Integer square root--returns the greatest integer less than or equal to the
// to the square root of n.
// This is Exercise 5.7 in Bressoud with my own modifications for better
// initial guess.
introot[n] :=
{
a = 2^((bitLength[n]+1) div 2)
//a = 2^((ceil[approxLog2[n]+1]) div 2)
b = a - 1
while b<a
{
// println["$a $b"]
a = b
b = (a*a + n) div (2*a)
}
return a
}
Download or view root.frink in plain text format
This is a program written in the programming language Frink.
For more information, view the Frink
Documentation or see More Sample Frink Programs.
Alan Eliasen was born 20115 days, 7 hours, 22 minutes ago.