Rewritten implementation of quadratic equation formula I just started learning the C programming language at the university, and today we got a new assignment there is about Refinement of a C software there is designed to find the roots of the general quadratic equation. We got an old C software that we have to rewrite with functions instead of using the Whole code inside main. We have to split the functions into 3 where is about 


Calculation of the discriminant.
Calculation of the first root.
Calculation of other root.


The old code:


#include &lt;stdio.h&gt;
#include &lt;math.h&gt;

/* Prints roots of the quadratic equation a * x*x + b * x + c = 0 */
void solveQuadraticEquation(double a, double b, double c){
  double discriminant, root1, root2;

  discriminant = b * b - 4 * a * c;

  if (discriminant &lt; 0)
    printf("No roots\n");
  else if (discriminant == 0){
    root1 = -b/(2*a);
    printf("One root: %f\n", root1);
  }
  else {
    root1 = (-b + sqrt(discriminant))/(2*a);
    root2 = (-b - sqrt(discriminant))/(2*a);
    printf("Two roots: %f and %f\n", root1, root2);
  }

}   

int main(void) {
  double a = 1.0, b = -8.0, c = 15.0,
         d = 2.0, e =  8.0, f =  2.0,
         g, h, i;

  /* First call - coefficents are values of variables */
  solveQuadraticEquation(a, b, c);  

  /* Second call - coefficents are values of expressions */
  solveQuadraticEquation(d - 1, -e, 7 * f + 1); 

  /* Third call - coefficents are entered by user outside 
solveQuadraticEquation */
  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &amp;g, &amp;h, &amp;i);
  solveQuadraticEquation(g, h, i);  

  return 0;
}



My Solution:

#include &lt;stdio.h&gt;
#include &lt;math.h&gt;

double discriminant(double a, double b, double c);
double root1(double a, double b, double c);
double root2(double a, double b, double c);

/* Prints roots of the quadratic equation a * x*x + b * x + c = 0 */
void solveQuadraticEquation(double a, double b, double c){

  if (discriminant(a, b, c) &lt; 0)
    printf("No roots\n");
  else if (discriminant(a, b, c) == 0){
    printf("One root: %f\n", root1(a, b, c));
  }
  else {
    printf("Two roots: %f and %f\n", root1(a, b, c), root2(a, b, c));
  }

}

int main(void) {
  double a = 1.0, b = -8.0, c = 15.0,
         d = 2.0, e =  8.0, f =  2.0,
         g, h, i;

  /* First call - coefficents are values of variables */
  solveQuadraticEquation(a, b, c);

  /* Second call - coefficents are values of expressions */
  solveQuadraticEquation(d - 1, -e, 7 * f + 1);

  /* Third call - coefficents are entered by user outside 
solveQuadraticEquation */
  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &amp;g, &amp;h, &amp;i);
  solveQuadraticEquation(g, h, i);

  return 0;
}

double discriminant(double a, double b, double c){
    return b * b - 4 * a * c;
}

double root1(double a, double b, double c){
  return (-b + sqrt(discriminant(a, b, c)))/(2*a);
}

double root2(double a, double b, double c){
  return (-b - sqrt(discriminant(a, b, c)))/(2*a);
}


I have already made a well-working solution, but I was wondering if I could make it even better, so if I have done something wrong or weird, please let me know.
Aside from re-computing discriminant(), a reasonable quadratic implementation.



When assessing the quality of floating-point computations, use "%e" rather than "%f".  I recommend using "%e" with enough precision too.  Like "%.17e", or use one of the DECIMAL_DIG family of constants.



Precision improvement when b*b near discriminant:

When |b| is about sqrt(discriminant), the computation of root1 or root2 will cancel many digits.  To avoid this, consider that a*root1*root2 = c

// calculate root1 and root2 together.
if (b &lt; 0) {
  root1 = (-b + sqrt(discriminant))/(2*a);
  root2 = c/(root1*a);
} else {
  root2 = (-b - sqrt(discriminant))/(2*a);
  root1 = c/(root2*a);
}


Try a=1.0, b = -1e24-1, c = 1e24.  Roots should be 1e24, 1 as here.  OP's approach  results in 1e24, 0, total loss of precision.  Keep in mind, FP has overall logarithmic precision, not linear.

When b*b and discriminant greatly differ, this approach is not significantly weaker than OP's original.

Precision improvements are possible with b * b - 4 * a * c, yet tend to impact performance more.



Advanced concern:

root1(), root2() is on thin ice at it relies on a non-negative result from discriminant().

C is sneaky in that FP code is allowed to use higher than needed precision selectively.  See FLT_EVAL_METHOD.  

With OP's code, this certainly will not happen as is, yet with a subtle change of in-lining b * b - 4 * a * c; instead of an explicit function call, may yield the following:

A test of b * b - 4 * a * c &lt; 0 may be false yet the later (-b + sqrt(b * b - 4 * a * c)) may attempt a square root of a negative value due to different optimizations.  

Solution: Use the same result rather than re-compute discriminant.  Do not rely on "same looking code" to generate the exact same FP result especially when near 0.0.

This also eliminates the wasteful re-computation of discriminant().



Check spelling on "coefficents": I'd expect "coefficients".



To meet OP's goal to split the functions into 3: discriminant, first root, other root consider: 

double discriminant(double a, double b, double c);
double first_root(double discriminant, double a, double b);
double other_root(double root1, double a, double c);

...
doubled = discriminant(a, b, c);
if (d &lt; 0) ...
else if (d == 0) ....
else {
  double root1 = first_root(discriminant, a, b);
  double root2 = other_root(root1, a, c);
  ...
}