Dayaan M. answered 6d
Earned A’s in Calc 1/AB & Calc 2/BC | 5 Years of Tutoring Experience
Given:
a = 1e-8
b = 10
c = 1e-8
x1 = ((-b) - sqrt(b**2 - 4*a*c)) / (2*a) (1st formula)
x2 = ((-b) + sqrt(b**2 - 4*a*c)) / (2*a) (2nd formula)
The problem is not how many decimals Python is printing. The problem is that the 2nd formula subtracts two numbers that are almost equal, and when that happens almost every correct digit gets cancelled out. This is called catastrophic cancellation.
Lets follow what happens inside the 2nd formula:
b**2 = 100
4*a*c = 4e-16
b**2 - 4*a*c = 100 - 4e-16 Subtract
= 100.0 A float only carries about 16 significant digits, so the 4e-16 falls off the end
sqrt(100.0) = 10.0 Take the square root
(-b) + 10.0 = -10 + 10.0 Plug into the top of the 2nd formula
= 0.0 Every correct digit is now gone
_______________________________
x2 = 0.0 / 2e-08 = 0.0
Notice the 1st formula never has this trouble, because (-b) and -sqrt(...) carry the same sign, so nothing cancels:
x1 = (-10 - 10.0) / 2e-08
= -20 / 2e-08 Simplify the top
= -1e+09 This value is already correct
So the fix is to compute the root that does not cancel, then get the other one from the fact that for a*x**2 + b*x + c = 0 the two roots multiply to c/a.
Firstly, we can write:
x1 * x2 = c/a Product of the roots
x2 = c / (a * x1) Divide by a*x1 on both sides to solve for x2
Now lets plug in the numbers:
x2 = 1e-8 / (1e-8 * -1e+09) Plug in a = 1e-8, c = 1e-8, x1 = -1e+09
= 1e-8 / -10.0 Simplify the bottom
= -1e-09
In code:
from math import sqrt
a = 1e-8
b = 10.0
c = 1e-8
d = sqrt(b*b - 4*a*c)
if b >= 0:
x1 = ((-b) - d) / (2*a)
else:
x1 = ((-b) + d) / (2*a)
x2 = c / (a * x1)
print 'x1 = {}'.format(x1)
print 'x2 = {}'.format(x2)
which gives:
x1 = -1000000000.0
x2 = -1e-09
The if statement is there so that the square root is always added to (-b) with the same sign, no matter whether b comes in positive or negative. That way the cancelling never happens on either root.
If you ever do need genuinely more digits and not just the correct answer, you can switch to the decimal module and raise the precision:
from decimal import Decimal, getcontext
getcontext().prec = 50
a = Decimal('1e-8')
but that is slower, and for this problem the rearrangement above is enough on its own.
x2 = -1e-09