As an exercise in learning Scala, I implemented a square root function like this:
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 betweensqrt
andMath.sqrt
. Although the bothsqrt
andMath.sqrt
are equally incorrect (the square of both have the same discrepancy withx
), I wonder if I can change the implementation to match the result ofMath.sqrt
.I found the error thresholds for
< 1
and>= 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 onx
and the numeric limits of the language.