11

I just came around and decided to try some Ada. The downside is that the syntax and function strays quite away from C++. So I had to like cram various stuff to get this thing to work.

My question is if there are some better way to do this calculation that what I have done here

   IF(B < 0.0) THEN
      B := ABS(B);
      X1 := (B / 2.0) + Sqrt( (B / 2.0) ** 2.0 + ABS(C));
      X2 := (B / 2.0) - Sqrt( (B / 2.0) ** 2.0 + ABS(C));
   ELSE
      X1 := -(B / 2.0) + Sqrt( (B / 2.0) ** 2.0 - C);
      X2 := -(B / 2.0) - Sqrt( (B / 2.0) ** 2.0 - C);
   END IF;

I had some problem with negative numbers, that's why I did a IF statement and used ABS() to turn those into positive. But the weird thing is that it works perfectly for the other case, which is strange...

starcorn
  • 8,261
  • 23
  • 83
  • 124
  • 2
    Regarding the first two lines - I'd avoid using abs() when you already know B is negative. Use B:=-B. Even if the compiler is smart and can inline stuff. – DarenW Jan 06 '11 at 23:21

5 Answers5

23

Solving quadratic equations is not as simple as most people think.

The standard formula for solving a x^2 + b x + c = 0 is

delta = b^2 - 4 a c
x1 = (-b + sqrt(delta)) / (2 a)   (*)
x2 = (-b - sqrt(delta)) / (2 a)

but when 4 a c << b^2, computing x1 involves subtracting close numbers, and makes you lose accuracy, so you use the following instead

delta as above
x1 = 2 c / (-b - sqrt(delta))     (**)
x2 = 2 c / (-b + sqrt(delta))

which yields a better x1, but whose x2 has the same problem as x1 had above.

The correct way to compute the roots is therefore

q = -0.5 (b + sign(b) sqrt(delta))

and use x1 = q / a and x2 = c / q, which I find very efficient. If you want to handle the case when delta is negative, or complex coefficients, then you must use complex arithmetic (which is quite tricky to get right too).

Edit: With Ada code:

DELTA := B * B - 4.0 * A * C;

IF(B > 0.0) THEN
    Q := -0.5 * (B + SQRT(DELTA));
ELSE
    Q := -0.5 * (B - SQRT(DELTA));
END IF;

X1 := Q / A;
X2 := C / Q;
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • Nice explanation! This is an excellent example of why the standard textbook formula for something may not be the best guide for writing code. – DarenW Jan 06 '11 at 23:19
  • Stylistic: It is normal for reserved words to be in lower case and identifiers in title case, and the parentheses are not needed in the 'if' statement, e.g. `if B > 0.0 then` and `Delta := etc`. – debater Feb 18 '22 at 21:12
  • Question: Are we using Floats here? In which case, would subtraction between close values really have an accuracy issue? – debater Feb 18 '22 at 21:13
  • 1
    @debater: Yes. 1.0000001 - 1 = 0.0000001 and you have lost 8 significant figures. If you're not convinced, try the above code and the high school formula with a=1, b=10000000, c=1. – Alexandre C. Feb 18 '22 at 23:54
2

Given ax2 + bx + c = 0 the quadradic formula gives solutions for x = (-b +/- sqrt(b2-4ac) ) / 2a. The discriminant d = b2-4ac will be positive for real valued roots, negative for roots with a non-zero imaginary component (i.e. a non-real complex number) and will be 0 when the root is a double root.

So, the Ada code for this would be:

D := B ** 2.0 - 4.0 * A * C;
IF D >= 0.0 THEN
  X1 := (-B + Sqrt(D)) / (2.0 * A);
  X2 := (-B - Sqrt(D)) / (2.0 * A);
ELSE
  -- Deal with the fact that the result is a non-real complex number.
END IF;

Note: I'm a bit rusty in Ada, but this should be close to the proper syntax.

andand
  • 17,134
  • 11
  • 53
  • 79
  • 1
    Close; all the numbers will need to be real (4.0 in the first line, 0.0 in the second, 2.0 in the third and fourth). Also, it's not necessary to put brackets round the conditional expression; `if D <= 0.0 then`. – Simon Wright Dec 22 '10 at 00:21
1

The quadratic formula is x = ( -b +/- sqrt ( b ** 2 - 4*a*c ) ) / ( 2 * a )

I'm guessing a is 1.

so x = -( b/2 ) +/- sqrt ( ( ( b ** 2 ) / 4 ) - c )

Calculate d = ( b ** 2 ) * 0.25 - c then check its sign.

If the sign of d is negative you have complex roots; handle them as you wish.

Replacing - c with + abs ( c ) if b happens to be negative will give you rubbish.

Usually multiplying by 0.5 or 0.25 is better than dividing by 2.0 or 4.0.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
0

Though I don't know Ada, I see following things that can be optimized:

  1. In the first branch of the IF instructiuon you already know that B is negative. So you can say B := -B instead of B := ABS(B). Or better: just use -B where you used B in the first branch.
  2. You are using the subexpression B/2.0 four times. It could be more efficient (and also more clear) to assign B/2.0 to an auxilary variable B_2 (or assign it to B again if you don't want to spend another variable) and use it instead.
    Also the sqrt is calculated twice. Assigning it to an auxilariy variable saves runtime (and makes it explicite for the reader that exactly the same subexpresion is used twice).
  3. Than it would probably be faster to use B_2*B_2 instead of **2.0; even better would be to use a dedicated square function, if there is one in Ada.
Curd
  • 12,169
  • 3
  • 35
  • 49
-1

To me, the question is more related to numeric algorithm than to Ada language. As always with numeric computing, one must often (if not -always-) refer to reference/academic papers.

These kinda questions always reminds me of this : https://en.wikipedia.org/wiki/Fast_inverse_square_root

You can only find the following tricks if you "do the maths" or find some paper that adresses your issue.

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the...? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

PS: as the wikiepdia article points out, this implementation is probably obsolete for most platforms now

LoneWanderer
  • 3,058
  • 1
  • 23
  • 41