Quadratic Equation Solver in C++, C, Java, Python and C#

A complete quadratic equation solver with tested code in five languages: discriminant cases, complex roots, edge cases and floating-point precision.

Quadratic equation solver: parabola crossing the x-axis at two root points

The quadratic solver is the perfect second program: bigger than “Hello, World”, small enough to finish in an evening, and secretly deep — it teaches input validation, branching on a computed value, floating-point behavior, and even complex numbers, all in forty lines. It is also a program most tutorials get subtly wrong.

This guide builds a complete, correct solver in C++ with a full walkthrough — every edge case from complex roots to bad input — then shows the same solver in C, Java, Python, and C#, and closes with the floating-point trap that breaks the textbook formula in production code. Every program was compiled and run: C++ and C with -Wall -Wextra, Java on OpenJDK 21, Python 3, C# on Mono — and every output shown, including the error cases and the precision experiment, is captured from those runs.

Table of Contents

The Math in 60 Seconds

A quadratic equation has the form ax² + bx + c = 0 (with a ≠ 0), and its roots come from the quadratic formula: x = (−b ± √(b² − 4ac)) / 2a. The expression under the square root, D = b² − 4ac, is the discriminant — its sign alone tells you what kind of roots exist before you compute them.

The Discriminant Decides: b² − 4ac D > 0 two distinct real roots D = 0 one repeated root no real roots — complex pair D < 0
  • D > 0 — the parabola crosses the x-axis twice: two distinct real roots.
  • D = 0 — the vertex touches the axis: one repeated real root, x = −b/2a.
  • D < 0 — the parabola never reaches the axis: no real roots, but a pair of complex conjugates, p ± qi.

A correct solver is exactly this decision, written in code — plus the cases the formula itself doesn’t cover: what if the user’s a is zero (not a quadratic at all), and what if the input isn’t a number?

The C++ Program

#include <cmath>
#include <iostream>

int main()
{
    std::cout << "Solve ax^2 + bx + c = 0\n";
    std::cout << "Enter coefficients a b c: ";

    double a, b, c;
    if (!(std::cin >> a >> b >> c)) {           // input validation
        std::cerr << "Invalid input: please enter three numbers.\n";
        return 1;
    }

    if (a == 0.0) {                             // not quadratic -- degenerate cases
        if (b != 0.0)
            std::cout << "Linear equation: one root x = " << -c / b << '\n';
        else if (c == 0.0)
            std::cout << "Every x is a solution (0 = 0).\n";
        else
            std::cout << "No solution: " << c << " = 0 is false.\n";
        return 0;
    }

    double d = b * b - 4.0 * a * c;             // the discriminant

    if (d > 0.0) {                              // two distinct real roots
        double sqrtD = std::sqrt(d);
        double x1 = (-b + sqrtD) / (2.0 * a);
        double x2 = (-b - sqrtD) / (2.0 * a);
        std::cout << "Discriminant = " << d << " > 0: two real roots\n";
        std::cout << "x1 = " << x1 << ", x2 = " << x2 << '\n';
    }
    else if (d == 0.0) {                        // one repeated real root
        double x = -b / (2.0 * a);
        std::cout << "Discriminant = 0: one repeated root\n";
        std::cout << "x = " << x << '\n';
    }
    else {                                      // complex conjugate pair
        double realPart = -b / (2.0 * a);
        double imagPart = std::sqrt(-d) / (2.0 * std::fabs(a));
        std::cout << "Discriminant = " << d << " < 0: complex roots\n";
        std::cout << "x1 = " << realPart << " + " << imagPart << "i\n";
        std::cout << "x2 = " << realPart << " - " << imagPart << "i\n";
    }
    return 0;
}

Compile with g++ -std=c++17 -Wall -Wextra quadratic.cpp -o quadratic. (any modern compiler works — see our guide to the best C++ compilers if you’re setting up). Here is the program handling every case — six real runs:

Input (a b c)Output
1 -5 6Discriminant = 1 > 0: two real rootsx1 = 3, x2 = 2
1 -4 4Discriminant = 0: one repeated rootx = 2
1 2 5Discriminant = -16 < 0: complex rootsx1 = -1 + 2i, x2 = -1 - 2i
0 2 -8Linear equation: one root x = 4
0 0 5No solution: 5 = 0 is false.
x y zInvalid input: please enter three numbers. (exit code 1)

Why it’s written this way

  • Input is validated once: if (!(std::cin >> a >> b >> c)) catches non-numeric input immediately instead of computing with garbage — and returns a non-zero exit code, the polite way for a program to report failure.
  • a == 0 is handled before the formula, because dividing by 2a would otherwise divide by zero. The degenerate ladder (linear → identity → contradiction) covers every input a user can actually type.
  • The discriminant is computed once and branched on three ways — the entire mathematical structure of the problem in four lines.
  • double, not float — twice the precision at no cost on modern hardware; precision matters here, as the section below proves.
  • Complex roots print correctly: the imaginary part uses std::fabs(a) so the output reads -1 + 2i and -1 - 2i — never the classic + -2i formatting glitch, and never a sign error when a is negative.

A note on design worth internalizing from this article’s own history: the previous version of this program enumerated eleven separate if-blocks over the sign combinations of a, b and c — and still produced misformatted output for negative coefficients. Computing the discriminant once collapses all of that into three branches, and the special cases that remain (a = 0, bad input) are genuinely special. When you find yourself writing the same formula in many branches, the branches are usually telling you a single computed value wants to exist.

When the Formula Lies: Floating-Point Precision

Here is the part that separates a homework solver from a production one. When is enormous compared to 4ac, then √(b²−4ac) is almost exactly |b| — and the formula’s −b + √(...) subtracts two nearly equal numbers, wiping out most of the significant digits. This is catastrophic cancellation, and it is not theoretical:

#include <cmath>
#include <iostream>
#include <iomanip>

int main()
{
    double a = 1.0, b = -1e8, c = 1.0;   // roots: ~1e8 and ~1e-8
    double sqrtD = std::sqrt(b * b - 4.0 * a * c);

    /* naive: both roots straight from the formula */
    double naive_big   = (-b + sqrtD) / (2.0 * a);
    double naive_small = (-b - sqrtD) / (2.0 * a);   // cancellation here!

    /* stable: compute the well-conditioned root first, derive the other
       from the identity x1 * x2 = c / a */
    double q = -0.5 * (b - sqrtD);       // b is negative, so this ADDS magnitudes
    double stable_big   = q / a;
    double stable_small = c / q;

    std::cout << std::setprecision(17);
    std::cout << "big root (both agree): " << naive_big
              << " vs " << stable_big << '\n';
    std::cout << "naive  small root: " << naive_small  << '\n';
    std::cout << "stable small root: " << stable_small << '\n';
    std::cout << "true   small root: ~1.0000000000000002e-08\n";
    return 0;
}

Output:

big root (both agree): 100000000 vs 100000000
naive  small root: 7.4505805969238281e-09
stable small root: 1e-08
true   small root: ~1.0000000000000002e-08

Read that middle line again: the textbook formula returned 7.45×10⁻⁹ for a root whose true value is 1.0×10⁻⁸ — a 25% error, in ordinary double precision, from a perfectly innocent-looking equation. The fix costs two lines: compute the root where −b and √D have the same sign (no cancellation), then get the other from the identity x₁·x₂ = c/a. For the interactive solver above, the naive formula is fine; the moment a solver processes real-world data — physics, graphics, finance — the stable version is the correct one, and knowing why is the kind of depth interviewers probe for.

The Same Solver in C, Java, Python and C#

The logic is identical everywhere — validate, handle a = 0, branch on the discriminant. Each version below was compiled and run with the input 1 2 5, and every language reports the same complex pair -1 ± 2i.

C — note the -lm flag: gcc -std=c11 -Wall -Wextra quadratic.c -o quadratic -lm (the math library must be linked explicitly, the classic C gotcha covered in our math.h reference):

#include <math.h>
#include <stdio.h>

int main(void)
{
    double a, b, c;
    printf("Enter coefficients a b c: ");
    if (scanf("%lf %lf %lf", &a, &b, &c) != 3) {
        fprintf(stderr, "Invalid input\n");
        return 1;
    }
    if (a == 0.0) {
        if (b != 0.0) printf("Linear: x = %g\n", -c / b);
        else          printf(c == 0.0 ? "Every x solves it\n" : "No solution\n");
        return 0;
    }

    double d = b * b - 4.0 * a * c;
    if (d > 0.0) {
        double s = sqrt(d);
        printf("Two real roots: x1 = %g, x2 = %g\n",
               (-b + s) / (2 * a), (-b - s) / (2 * a));
    } else if (d == 0.0) {
        printf("One repeated root: x = %g\n", -b / (2 * a));
    } else {
        printf("Complex roots: %g +/- %gi\n",
               -b / (2 * a), sqrt(-d) / (2 * fabs(a)));
    }
    return 0;
}

Java (OpenJDK 21):

import java.util.Scanner;

public class Quadratic {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        System.out.print("Enter coefficients a b c: ");
        double a = in.nextDouble(), b = in.nextDouble(), c = in.nextDouble();

        if (a == 0) {
            System.out.println(b != 0 ? "Linear: x = " + (-c / b)
                                      : (c == 0 ? "Every x solves it" : "No solution"));
            return;
        }
        double d = b * b - 4 * a * c;
        if (d > 0) {
            double s = Math.sqrt(d);
            System.out.printf("Two real roots: x1 = %g, x2 = %g%n",
                              (-b + s) / (2 * a), (-b - s) / (2 * a));
        } else if (d == 0) {
            System.out.printf("One repeated root: x = %g%n", -b / (2 * a));
        } else {
            System.out.printf("Complex roots: %g +/- %gi%n",
                              -b / (2 * a), Math.sqrt(-d) / (2 * Math.abs(a)));
        }
    }
}

Python 3 — the shortest of the five, because the cmath module makes the discriminant branching optional: cmath.sqrt happily takes negative numbers, so one formula covers all three cases:

import cmath

a, b, c = map(float, input("Enter coefficients a b c: ").split())

if a == 0:
    print(f"Linear: x = {-c / b}" if b != 0
          else ("Every x solves it" if c == 0 else "No solution"))
else:
    d = b * b - 4 * a * c
    r1 = (-b + cmath.sqrt(d)) / (2 * a)   # cmath handles d < 0 for free
    r2 = (-b - cmath.sqrt(d)) / (2 * a)
    kind = "real" if d >= 0 else "complex"
    print(f"Discriminant {d:g}: {kind} roots {r1:g} and {r2:g}")

One quirk from the real run: for 1 -5 6 this prints real roots 3+0j and 2+0jcmath returns complex numbers even when the roots are real (Python spells the imaginary unit j). Take .real when D ≥ 0 if you want plain numbers.

C# (compiles with Mono or .NET):

using System;

class QuadraticSolver
{
    static void Main()
    {
        Console.Write("Enter coefficients a b c: ");
        string[] parts = Console.ReadLine().Split(' ');
        double a = double.Parse(parts[0]), b = double.Parse(parts[1]),
               c = double.Parse(parts[2]);

        if (a == 0)
        {
            Console.WriteLine(b != 0 ? $"Linear: x = {-c / b}"
                              : (c == 0 ? "Every x solves it" : "No solution"));
            return;
        }
        double d = b * b - 4 * a * c;
        if (d > 0)
        {
            double s = Math.Sqrt(d);
            Console.WriteLine($"Two real roots: x1 = {(-b + s) / (2 * a)}, x2 = {(-b - s) / (2 * a)}");
        }
        else if (d == 0)
            Console.WriteLine($"One repeated root: x = {-b / (2 * a)}");
        else
            Console.WriteLine($"Complex roots: {-b / (2 * a)} +/- {Math.Sqrt(-d) / (2 * Math.Abs(a))}i");
    }
}

Key Takeaways

  • Compute the discriminant D = b² − 4ac once and branch three ways — D > 0 (two real roots), D = 0 (one repeated root), D < 0 (complex pair p ± qi). That single computed value replaces any pile of special cases.
  • Validate before you calculate: check that the input parsed at all, and handle a = 0 (linear, identity, or contradiction) before dividing by 2a.
  • Use double, and print complex roots with the absolute value of the imaginary part — the + -2i glitch and the negative-a sign error are the two classic output bugs.
  • The textbook formula can lose 25% of a root to catastrophic cancellation when b² ≫ 4ac; the stable fix computes the well-conditioned root first and derives the other from x₁·x₂ = c/a.
  • The logic ports one-to-one across languages — the differences are I/O idioms; Python’s cmath even collapses the three cases into one formula.
  • In C, remember -lm — the math functions live in a separate library at link time.

Frequently Asked Questions

Conclusion

Forty lines of code, and yet the quadratic solver touches half of what real programming is about: trusting nothing from the user, handling the inputs the formula forgot, and discovering that floating-point arithmetic keeps its own opinions about your mathematics. If you got each of the six test cases working — including the ones that refuse to compute — you have written a genuinely robust program, in whichever of the five languages you chose.

Two good next steps: rewrite your version using the numerically stable formula as the default, and then pick a second language from this page and port it yourself — nothing teaches the difference between syntax and logic faster. More projects like this live in our programming section.

Scroll to Top