HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Sqrt (square root) function

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
sqrtfunctionrootsquare

Problem

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 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 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.