1. Overview

A quadratic equation is a second-degree polynomial equation in a single variable, typically written in the form:

ax^2 + bx + c = 0

We can find such equations in various real-world applications in finance, computer graphics, academics, etc. So, it’s a fundamental mathematical operation with diverse applications to know.

In this tutorial, we’ll explore a few approaches to finding the roots of a quadratic equation in Kotlin.

2. Algorithm

Let’s take a quadratic equation with a, b, and c as the coefficient constants for the polynomial terms in decreasing order of degree:

a*x^2 + b*x + c = 0

Now, the next step is to find the discriminant (D) in terms of the three coefficients:

D = b^2 - 4*a*c

It’s important to note that the discriminant (D) plays a significant role in deciding the nature of roots for the quadratic equation:

  • When D>0, both the roots are real numbers
  • When D<0, both the roots are complex numbers with an imaginary component
  • When D==0, both the roots are real numbers with the same value

Further, we can express the roots in terms of the discriminant (D) and coefficients:

root1 = (-b + √D) / (2a)
root2 = (-b - √D) / (2a)

Lastly, we can assume that a!=0 for the equation to be quadratic, as the coefficient of the highest degree term needs to be non-zero. As a result, we can safely divide by a while computing the roots.

3. Solving for Real Roots

In this section, we’ll keep things simple and implement the algorithm for scenarios when the roots are real numbers.

3.1. QuadraticEquationSolver Class

Let’s start by writing the barebone class, QuadraticEquationSolver, with three properties, a, b, and c, denoting the coefficients of the quadratic equation:

class QuadraticEquationSolver(val a: Double, val b: Double, val c: Double) {
    // ToDo: Add solutions
}

We must note that the properties, namely, a, b, and c, will be initialized through the primary constructor. Further, we intend to add the solution for finding roots within this class.

3.2. Solving for Real Roots

Now, let’s go ahead and write the solveForRealRoots() function within the QuadraticEquationSolver class to compute the real roots for the equation:

fun solveForRealRoots(): Pair<Double, Double>? {
    val discriminant = b * b - 4 * a * c
    if (discriminant < 0) {
        // No real roots
        return null
    }
    val root1 = (-b + sqrt(discriminant)) / (2 * a)
    val root2 = (-b - sqrt(discriminant)) / (2 * a)
    return Pair(root1, root2)
}

It’s worth noting that we check for the value of the discriminant to be non-negative before finding the real roots. Moreover, we return a Pair of  root1 and root2.

Lastly, let’s compute the roots of the equation x^2 + 2*x + 1 = 0 and verify that our function is giving the correct results:

val solver = QuadraticEquationSolver(1.0, 2.0, 1.0)
val expectedRoots = Pair(-1.0, -1.0)
val actualRoots = solver.solveForRealRoots()
Assertions.assertEquals(expectedRoots, actualRoots)

As expected, we’ve got the correct roots using this approach.

4. Solving for Complex Roots

In this section, let’s learn how to implement the algorithm to compute complex roots for a quadratic equation.

4.1. Commons Math Library

To support arithmetic operations for complex numbers, we’ll use the commons-math3 library. So, let’s go ahead and add its dependency to the pom.xml file in our project:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.6.1</version>
</dependency>

After adding the dependency, we can use the Complex class from the org.apache.commons.math3.complex package to carry out arithmetic operations for complex numbers conveniently.

4.2. Complex Roots

Let’s go ahead and write the solveForComplexRoots() function to return a Pair of Complex roots for a quadratic equation:

fun solveForComplexRoots(): Pair<Complex, Complex>? {
    val discriminant = Complex(b * b - 4 * a * c)
    val sqrtDiscriminant = discriminant.sqrt()

    val root1 = (Complex(-b).add(sqrtDiscriminant)).divide(Complex(2 * a))
    val root2 = (Complex(-b).subtract(sqrtDiscriminant)).divide(Complex(2 * a))

    return Pair(root1, root2)
}

We can see that our approach no longer needs to check whether the discriminant is non-zero. Further, we’ve used the sqrt(), add(), subtract(), and divide() functions from the Complex class for performing arithmetic operations.

Next, let’s use the solveForComplexRoots() to find the real roots for the equation x^2 + 2*x + 1 = 0:

val solver = QuadraticEquationSolver(1.0, 2.0, 1.0)
val expectedRoots = Pair(Complex(-1.0), Complex(-1.0))
val actualRoots = solver.solveForComplexRoots()
Assertions.assertEquals(expectedRoots, actualRoots)

It’s important to remember that all real numbers are a subset of complex numbers. So, we can use solveForComplexRoots() for any generic quadratic equation.

Lastly, let’s verify that it works for the equation x^2 + x + 1 = 0, which has complex roots with imaginary components:

val solver = QuadraticEquationSolver(1.0, 1.0, 1.0)
val expectedRoots = Pair(Complex(-0.5, Math.sqrt(3.0) * 0.5), Complex(-0.5, -1 * Math.sqrt(3.0) * 0.5))
val actualRoots = solver.solveForComplexRoots()
Assertions.assertEquals(expectedRoots, actualRoots)

Great! We’ve got this one right.

5. Conclusion

In this article, we learned about the basics of a quadratic equation and how to find its roots. Furthermore, we explored two scenarios where roots can be real numbers or complex numbers with an imaginary component, respectively.

As always, the code from this article is available over on GitHub.

Comments are closed on this article!