patternMinor
Sqrt (square root) function
Viewed 0 times
sqrtfunctionrootsquare
Problem
As an exercise in learning Scala, I implemented a square root function like this:
The implementation passes these unit tests:
My questions:
-
How can I improve this?
-
Notice the unit test for the case of
-
I found the error thresholds for
def sqrt(x: Double): Double = {
if (x < 0) throw new IllegalArgumentException("negative numbers not allowed")
val threshold = if (x < 1) x / 1e15 else 1e-12
def sqrt(x: Double, p: Double): Double = {
if (p == x / p) p // without this condition, non-termination with 1e50
else if (Math.abs(p * p - x) < threshold) {
def diff1 = Math.abs(x - p * p)
def diff2 = Math.abs(x - x / p * x / p)
if (diff1 < diff2) p else x / p
}
else sqrt(x, (p + x / p) / 2)
}
sqrt(x, x / 2)
}The implementation passes these unit tests:
test("sqrt 2") {
assert(sqrt(2) === Math.sqrt(2))
}
test("sqrt 1e-3") {
assert(Math.abs(1e-3 - sqrt(1e-3) * sqrt(1e-3)) ===
Math.abs(1e-3 - Math.sqrt(1e-3) * Math.sqrt(1e-3)))
}
test("sqrt 1e-20") {
assert(sqrt(1e-20) === Math.sqrt(1e-20))
}
test("sqrt 1e-21") {
assert(sqrt(1e-21) === Math.sqrt(1e-21))
}
test("sqrt 1e20") {
assert(sqrt(1e20) === Math.sqrt(1e20))
}
test("sqrt 1e50") {
assert(sqrt(1e50) === Math.sqrt(1e50))
}My questions:
-
How can I improve this?
-
Notice the unit test for the case of
1e-3. It's more complex than the others to compensate for the difference between sqrt and Math.sqrt. Although the both sqrt and Math.sqrt are equally incorrect (the square of both have the same discrepancy with x), I wonder if I can change the implementation to match the result of Math.sqrt.-
I found the error thresholds for
= 1 through trial and error: all unit tests pass with these values and some would fell if I stretched the limits further. I'm wondering if there is a better, proper way of setting suitable thresholds based on x and the numeric limits of the language.Solution
I'm going to concentrate on the Scala rather than the maths side, because that's my area.
The internal helper function doesn't need two parameters, since the value of x never changes; pushing x repeatedly onto the stack is a waste of time. Also, better to use a case statement than a chain of
I renamed the inner function sqrtx to avoid confusion about what it is doing.
The internal helper function doesn't need two parameters, since the value of x never changes; pushing x repeatedly onto the stack is a waste of time. Also, better to use a case statement than a chain of
if...else if...else - much less error prone.def sqrt(x: Double): Double = {
if (x q // without this condition, non-termination with 1e50
case q if Math.abs(q * q - x) {
def diff1 = Math.abs(x - p * p)
def diff2 = Math.abs(x - x / p * x / p)
if (diff1 sqrtx((p + x / p) / 2)
}
sqrtx(x / 2)
}I renamed the inner function sqrtx to avoid confusion about what it is doing.
Code Snippets
def sqrt(x: Double): Double = {
if (x < 0) throw new IllegalArgumentException("negative numbers not allowed")
val threshold = if (x < 1) x / 1e15 else 1e-12
def sqrtx(p: Double): Double = p match {
case q if q == x / q => q // without this condition, non-termination with 1e50
case q if Math.abs(q * q - x) < threshold => {
def diff1 = Math.abs(x - p * p)
def diff2 = Math.abs(x - x / p * x / p)
if (diff1 < diff2) p else x / p
}
case _ => sqrtx((p + x / p) / 2)
}
sqrtx(x / 2)
}Context
StackExchange Code Review Q#31628, answer score: 7
Revisions (0)
No revisions yet.