Quadratic solver
ax^2 + bx + c = 0 with exact rational, irrational and complex roots, square roots and divisions worked digit by digit on whole numbers; and the small root of x^2 + bx + 1 in floats, by the textbook formula and a stable one, against its exact digits.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
ax² + bx + c = 0, solved exactly: rational, irrational and complex roots. The program also solves x² + bx + 1 in floats, by the textbook formula and by a stable one, against the root's exact digits.
main.eml- the menu, the equation and its roots, and the float comparisonquad.eml- reading coefficients, square roots and divisions digit by digit, the solver, a float square root, and the small root two ways and exactly
How each part works:
- Coefficients have at most three decimals. They are scaled by 1000 to whole numbers, which leaves the roots as they are, so the discriminant b² - 4ac is exact.
- The kind of root, as in the corpus case
quadratic-solver, here with complex roots too: - a discriminant of 0 gives one double root; - the square of a fraction gives two rational roots; - any other positive one gives two irrational roots; - a negative one gives complex roots.
A leading coefficient below zero is turned positive by changing every sign, which leaves the roots as they are. - Square roots use the digit-pair method and divisions use long division, both on whole numbers. Only adding, subtracting, multiplying and comparing are used, so the 12 decimal places are exact: 18 are worked out, and then rounded half away from zero. - Floats, as in the corpus case catastrophic-cancellation: for x² + bx + 1 with b large, the textbook formula takes -b + √(b² - 4) when the two are nearly equal, and the digits they share cancel. The stable formula works out the big root, where nothing cancels, and divides: the two roots multiply to 1. The float square root is Newton's method from above, so it is the same in every Python. The program measures each printed float's relative error against 20 exact digits.
What is checked: menu choices 1 to 3; coefficients with at most 9 digits before the point and 3 after; b from 3 to 10¹² for the float comparison. An empty answer cancels.
Sessions: sessions/basic.in gives: - x² - 3x + 2 and 2x² - 7x + 3, with rational roots; - x² - 2, with irrational roots, and x² + 2x + 5, with complex roots; - x² - 2x + 1, a double root; - 2x - 4, 0 = 0 and 5 = 0, where a, or everything, is 0; - -x² + 2, and 0.5x² - 0.125x + 0.007; - the float comparison for b = 3, 10⁸ (the textbook root off by 49%) and 10¹² (off by 100%: it comes out as 0).
sessions/bad-input.in gives: - menu choices 0 and x; - coefficients: a cancel, abc, four decimals, two later cancels, a comma and ten digits; - the float comparison with a cancel, 2, 10¹² + 1 and x.
Built on the verified corpus cases quadratic-solver (the quadratic formula and its discriminant) and catastrophic-cancellation (the same algebra, and one arrangement keeps its digits).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Quadratic solver ==
ax^2 + bx + c = 0, solved exactly - and the same algebra in floats losing its small root.
1) solve 2) floats and cancellation 3) quit
choice> 0
Pick a number from 1 to 3.
1) solve 2) floats and cancellation 3) quit
choice> x
Pick a number from 1 to 3.
1) solve 2) floats and cancellation 3) quit
choice> 1
a>
Cancelled.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> abc
Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1.2345
Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b>
Cancelled.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> 2
c>
Cancelled.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> 2
c> 1,5
Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 9999999999
Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000>
Cancelled.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> 2
Type a whole number from 3 to 1000000000000.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> 1000000000001
Type a whole number from 3 to 1000000000000.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> x
Type a whole number from 3 to 1000000000000.
1) solve 2) floats and cancellation 3) quit
choice> 3
Bye.
What was typed (30 lines)
0
x
1
1
abc
1
1.2345
1
1
1
1
2
1
1
2
1,5
1
9999999999
2
2
2
2
1000000000001
2
x
3
basic
interpreter: byte-equal== Quadratic solver ==
ax^2 + bx + c = 0, solved exactly - and the same algebra in floats losing its small root.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> -3
c> 2
x^2 - 3x + 2 = 0
Discriminant b^2 - 4ac = 1.
It is the square of a fraction: two rational roots, x = 1 and x = 2.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 2
b> -7
c> 3
2x^2 - 7x + 3 = 0
Discriminant b^2 - 4ac = 25.
It is the square of a fraction: two rational roots, x = 1/2 and x = 3.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> 0
c> -2
x^2 - 2 = 0
Discriminant b^2 - 4ac = 8.
It is positive and not the square of a fraction: two irrational roots, x = (-b - sqrt(D)) / 2a and (-b + sqrt(D)) / 2a:
x = -1.414213562373
x = 1.414213562373
to 12 places, each digit worked out exactly.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> 2
c> 5
x^2 + 2x + 5 = 0
Discriminant b^2 - 4ac = -16.
It is negative: two complex roots, x = (-b / 2a) - i (sqrt(-D) / 2a) and its conjugate:
x = -1 - 2.000000000000i
x = -1 + 2.000000000000i
the imaginary part to 12 places.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 1
b> -2
c> 1
x^2 - 2x + 1 = 0
Discriminant b^2 - 4ac = 0.
It is zero: one double root, x = 1.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 0
b> 2
c> -4
2x - 4 = 0
With a = 0 it is not quadratic: one root, x = 2.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 0
b> 0
c> 0
0 = 0
Every x solves it: all three coefficients are zero.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 0
b> 0
c> 5
5 = 0
No x solves it: it says a number other than zero equals zero.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> -1
b> 0
c> 2
-x^2 + 2 = 0
Discriminant b^2 - 4ac = 8.
It is positive and not the square of a fraction: two irrational roots, x = (-b - sqrt(D)) / 2a and (-b + sqrt(D)) / 2a:
x = -1.414213562373
x = 1.414213562373
to 12 places, each digit worked out exactly.
1) solve 2) floats and cancellation 3) quit
choice> 1
a> 0.5
b> -0.125
c> 0.007
0.5x^2 - 0.125x + 0.007 = 0
Discriminant b^2 - 4ac = 0.001625.
It is positive and not the square of a fraction: two irrational roots, x = (-b - sqrt(D)) / 2a and (-b + sqrt(D)) / 2a:
x = 0.084688711259
x = 0.165311288741
to 12 places, each digit worked out exactly.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> 3
x^2 + 3x + 1 = 0, its root nearest zero:
exact, 20 digits: -3.8196601125010515179e-1
textbook formula: -0.3819660112501051 (right to about 15 significant digits)
stable formula: -0.38196601125010515 (right to 17 significant digits or more)
The textbook formula takes -b + sqrt(b^2 - 4) when the two are nearly equal, and the digits they share cancel. The stable one works out the big root, where nothing cancels, and divides: the product of the roots is c / a = 1.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> 100000000
x^2 + 100000000x + 1 = 0, its root nearest zero:
exact, 20 digits: -1.0000000000000001000e-8
textbook formula: -1.4901161193847656e-08 (off by 49%)
stable formula: -1.0000000000000002e-08 (right to about 16 significant digits)
The textbook formula takes -b + sqrt(b^2 - 4) when the two are nearly equal, and the digits they share cancel. The stable one works out the big root, where nothing cancels, and divides: the product of the roots is c / a = 1.
1) solve 2) floats and cancellation 3) quit
choice> 2
b, a whole number from 3 to 1000000000000> 1000000000000
x^2 + 1000000000000x + 1 = 0, its root nearest zero:
exact, 20 digits: -1.0000000000000000000e-12
textbook formula: 0.0 (off by 100%)
stable formula: -1e-12 (right to 17 significant digits or more)
The textbook formula takes -b + sqrt(b^2 - 4) when the two are nearly equal, and the digits they share cancel. The stable one works out the big root, where nothing cancels, and divides: the product of the roots is c / a = 1.
1) solve 2) floats and cancellation 3) quit
choice> 3
Bye.
What was typed (47 lines)
1
1
-3
2
1
2
-7
3
1
1
0
-2
1
1
2
5
1
1
-2
1
1
0
2
-4
1
0
0
0
1
0
0
5
1
-1
0
2
1
0.5
-0.125
0.007
2
3
2
100000000
2
1000000000000
3
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P114 quadratic solver: exact roots of ax^2 + bx + c = 0, real or complex,
# and the small root of x^2 + bx + 1 in floats, by the textbook formula and
# the stable one, against its exact digits.
import quad
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s, most):
if len(s) == 0 or len(s) > most:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def ask_coef(name):
# [coefficient x 1000, text], [] to cancel, ["!"] after a complaint
plain(input(name + "> ")) => t
if t == "":
return []
quad.parse_coef(t) => c
if len(c) == 0:
"Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125." ^0
return ["!"]
return [c[0], quad.trimmed(quad.scaled_text(c[0], 3))]
def equation(a, b, c):
"" => out
for part in [[a, "x^2"], [b, "x"], [c, ""]]:
part[0] => v
if v[0] != 0:
v[1] => t
"+" => sign
if t[0] == "-":
"-" => sign
t[1:len(t)] => t
if (t == "1") and part[1] != "":
"" => t
if out == "":
if sign == "-":
"-" => out
out + t + part[1] => out
else:
out + " " + sign + " " + t + part[1] => out
if out == "":
"0" => out
return out + " = 0"
def solve_menu():
ask_coef("a") => a
if len(a) == 0:
"Cancelled." ^0
return
if a[0] == "!":
return
ask_coef("b") => b
if len(b) == 0:
"Cancelled." ^0
return
if b[0] == "!":
return
ask_coef("c") => c
if len(c) == 0:
"Cancelled." ^0
return
if c[0] == "!":
return
equation(a, b, c) ^0
quad.solve(a[0], b[0], c[0]) => r
if r[0] == "all":
"Every x solves it: all three coefficients are zero." ^0
return
if r[0] == "none":
"No x solves it: it says a number other than zero equals zero." ^0
return
if r[0] == "linear":
("With a = 0 it is not quadratic: one root, x = " + r[1] + ".") ^0
return
b[0] * b[0] - 4 * a[0] * c[0] => D
("Discriminant b^2 - 4ac = " + quad.trimmed(quad.scaled_text(D, 6)) + ".") ^0
if r[0] == "double":
("It is zero: one double root, x = " + r[1] + ".") ^0
elif r[0] == "two rational":
("It is the square of a fraction: two rational roots, x = " + r[1] + " and x = " + r[2] + ".") ^0
elif r[0] == "two real":
"It is positive and not the square of a fraction: two irrational roots, x = (-b - sqrt(D)) / 2a and (-b + sqrt(D)) / 2a:" ^0
(" x = " + r[2]) ^0
(" x = " + r[3]) ^0
"to 12 places, each digit worked out exactly." ^0
else:
"It is negative: two complex roots, x = (-b / 2a) - i (sqrt(-D) / 2a) and its conjugate:" ^0
(" x = " + r[1] + " - " + r[2] + "i") ^0
(" x = " + r[1] + " + " + r[2] + "i") ^0
"the imaginary part to 12 places." ^0
def printed_value(x):
# [M, e] with the printed float equal to M x 10^e exactly, M >= 0
str(x) => t
if t[0] == "-":
t[1:len(t)] => t
0 => e
-1 => at
for i in [0:len(t) - 1]:
if at == -1 and t[i] == "e":
i => at
if at >= 0:
t[at + 1:len(t)] => es
1 => sign
if es[0] == "-":
0 - 1 => sign
es[1:len(es)] => es
elif es[0] == "+":
es[1:len(es)] => es
sign * int(es) => e
t[0:at] => t
"" => ds
0 => after
False => dot
for c in t:
if c == ".":
True => dot
else:
ds + c => ds
if dot:
after + 1 => after
return [int(ds), e - after]
def ten_to(k):
"1" => t
for i in [1:k]:
t + "0" => t
return int(t)
def quality(x, ex):
# how close the printed float x is to the exact root, -digits x
# 10^(lead - 19): [k, -1] with k the largest up to 17 whose relative
# error is at most 10^-k, or [0, whole percent] when it is over 10%
printed_value(x) => p
int(ex[0]) => m
ex[1] - 19 => e
p[1] => lo
if e < lo:
e => lo
p[0] * ten_to(p[1] - lo) => xv
if x < 0.0:
0 - xv => xv
0 - m * ten_to(e - lo) => ev
xv - ev => diff
if diff < 0:
0 - diff => diff
0 - ev => size
0 => k
while k < 17 and diff * ten_to(k + 1) <= size:
k + 1 => k
if k >= 1:
return [k, 0 - 1]
return [0, quad.quotient_digits(diff * 100, size, 0)]
def verdict(q):
if q[1] >= 0:
return "off by " + str(q[1]) + "%"
if q[0] >= 17:
return "right to 17 significant digits or more"
return "right to about " + str(q[0]) + " significant digits"
def cancellation():
plain(input("b, a whole number from 3 to 1000000000000> ")) => t
if t == "":
"Cancelled." ^0
return
number(t, 13) => b
if b < 3 or b > 1000000000000:
"Type a whole number from 3 to 1000000000000." ^0
return
quad.small_root(b) => r
quad.exact_small(b, 20) => ex
("x^2 + " + str(b) + "x + 1 = 0, its root nearest zero:") ^0
(" exact, 20 digits: -" + ex[0][0:1] + "." + ex[0][1:len(ex[0])] + "e" + str(ex[1])) ^0
(" textbook formula: " + str(r[0]) + " (" + verdict(quality(r[0], ex)) + ")") ^0
(" stable formula: " + str(r[1]) + " (" + verdict(quality(r[1], ex)) + ")") ^0
"The textbook formula takes -b + sqrt(b^2 - 4) when the two are nearly equal, and the digits they share cancel. The stable one works out the big root, where nothing cancels, and divides: the product of the roots is c / a = 1." ^0
"== Quadratic solver ==" ^0
"ax^2 + bx + c = 0, solved exactly - and the same algebra in floats losing its small root." ^0
True => running
while running:
"" ^0
"1) solve 2) floats and cancellation 3) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
solve_menu()
elif choice == "2":
cancellation()
elif choice == "3":
False => running
else:
"Pick a number from 1 to 3." ^0
"Bye." ^0
Python projection (main.py)
import quad
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s, most):
if len(s) == 0 or len(s) > most:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def ask_coef(name):
t = plain(input(name + "> "))
if t == "":
return []
c = quad.parse_coef(t)
if len(c) == 0:
print("Type a number with at most 9 digits before the point and 3 after, such as -2 or 0.125.")
return ["!"]
return [c[0], quad.trimmed(quad.scaled_text(c[0], 3))]
def equation(a, b, c):
out = ""
for part in [[a, "x^2"], [b, "x"], [c, ""]]:
v = part[0]
if v[0] != 0:
t = v[1]
sign = "+"
if t[0] == "-":
sign = "-"
t = t[1:len(t)]
if t == "1" and part[1] != "":
t = ""
if out == "":
if sign == "-":
out = "-"
out = out + t + part[1]
else:
out = out + " " + sign + " " + t + part[1]
if out == "":
out = "0"
return out + " = 0"
def solve_menu():
a = ask_coef("a")
if len(a) == 0:
print("Cancelled.")
return
if a[0] == "!":
return
b = ask_coef("b")
if len(b) == 0:
print("Cancelled.")
return
if b[0] == "!":
return
c = ask_coef("c")
if len(c) == 0:
print("Cancelled.")
return
if c[0] == "!":
return
print(equation(a, b, c))
r = quad.solve(a[0], b[0], c[0])
if r[0] == "all":
print("Every x solves it: all three coefficients are zero.")
return
if r[0] == "none":
print("No x solves it: it says a number other than zero equals zero.")
return
if r[0] == "linear":
print("With a = 0 it is not quadratic: one root, x = " + r[1] + ".")
return
D = b[0] * b[0] - 4 * a[0] * c[0]
print("Discriminant b^2 - 4ac = " + quad.trimmed(quad.scaled_text(D, 6)) + ".")
if r[0] == "double":
print("It is zero: one double root, x = " + r[1] + ".")
elif r[0] == "two rational":
print("It is the square of a fraction: two rational roots, x = " + r[1] + " and x = " + r[2] + ".")
elif r[0] == "two real":
print("It is positive and not the square of a fraction: two irrational roots, x = (-b - sqrt(D)) / 2a and (-b + sqrt(D)) / 2a:")
print(" x = " + r[2])
print(" x = " + r[3])
print("to 12 places, each digit worked out exactly.")
else:
print("It is negative: two complex roots, x = (-b / 2a) - i (sqrt(-D) / 2a) and its conjugate:")
print(" x = " + r[1] + " - " + r[2] + "i")
print(" x = " + r[1] + " + " + r[2] + "i")
print("the imaginary part to 12 places.")
def printed_value(x):
t = str(x)
if t[0] == "-":
t = t[1:len(t)]
e = 0
at = -1
for i in range(0, len(t)):
if at == -1 and t[i] == "e":
at = i
if at >= 0:
es = t[at + 1:len(t)]
sign = 1
if es[0] == "-":
sign = 0 - 1
es = es[1:len(es)]
elif es[0] == "+":
es = es[1:len(es)]
e = sign * int(es)
t = t[0:at]
ds = ""
after = 0
dot = False
for c in t:
if c == ".":
dot = True
else:
ds = ds + c
if dot:
after = after + 1
return [int(ds), e - after]
def ten_to(k):
t = "1"
for i in range(1, k+1):
t = t + "0"
return int(t)
def quality(x, ex):
p = printed_value(x)
m = int(ex[0])
e = ex[1] - 19
lo = p[1]
if e < lo:
lo = e
xv = p[0] * ten_to(p[1] - lo)
if x < 0.0:
xv = 0 - xv
ev = 0 - m * ten_to(e - lo)
diff = xv - ev
if diff < 0:
diff = 0 - diff
size = 0 - ev
k = 0
while k < 17 and diff * ten_to(k + 1) <= size:
k = k + 1
if k >= 1:
return [k, 0 - 1]
return [0, quad.quotient_digits(diff * 100, size, 0)]
def verdict(q):
if q[1] >= 0:
return "off by " + str(q[1]) + "%"
if q[0] >= 17:
return "right to 17 significant digits or more"
return "right to about " + str(q[0]) + " significant digits"
def cancellation():
t = plain(input("b, a whole number from 3 to 1000000000000> "))
if t == "":
print("Cancelled.")
return
b = number(t, 13)
if b < 3 or b > 1000000000000:
print("Type a whole number from 3 to 1000000000000.")
return
r = quad.small_root(b)
ex = quad.exact_small(b, 20)
print("x^2 + " + str(b) + "x + 1 = 0, its root nearest zero:")
print(" exact, 20 digits: -" + ex[0][0:1] + "." + ex[0][1:len(ex[0])] + "e" + str(ex[1]))
print(" textbook formula: " + str(r[0]) + " (" + verdict(quality(r[0], ex)) + ")")
print(" stable formula: " + str(r[1]) + " (" + verdict(quality(r[1], ex)) + ")")
print("The textbook formula takes -b + sqrt(b^2 - 4) when the two are nearly equal, and the digits they share cancel. The stable one works out the big root, where nothing cancels, and divides: the product of the roots is c / a = 1.")
print("== Quadratic solver ==")
print("ax^2 + bx + c = 0, solved exactly - and the same algebra in floats losing its small root.")
running = True
while running:
print("")
print("1) solve 2) floats and cancellation 3) quit")
choice = plain(input("choice> "))
if choice == "1":
solve_menu()
elif choice == "2":
cancellation()
elif choice == "3":
running = False
else:
print("Pick a number from 1 to 3.")
print("Bye.")
quad.eml
eml# P114 quadratic solver - ax^2 + bx + c = 0 with coefficients of at most
# three decimals, scaled by 1000 to whole numbers so the discriminant
# b^2 - 4ac is exact (the corpus case quadratic-solver, here with complex
# roots too). Square roots and divisions are done digit by digit on whole
# numbers - adding, subtracting, multiplying and comparing only - so every
# printed digit is exact. And as in the corpus case
# catastrophic-cancellation, the textbook formula in floats loses the small
# root of x^2 + bx + 1 when b is large, while the stable one keeps it.
def gcd(a, b):
if a < 0:
0 - a => a
if b < 0:
0 - b => b
while b != 0:
a % b => r
b => a
r => b
return a
def parse_coef(t):
# the coefficient times 1000, or [] (at most 9 digits before the point
# and 3 after)
if len(t) == 0:
return []
1 => sign
t => s
if s[0] == "-":
0 - 1 => sign
s[1:len(s)] => s
"" => whole
"" => frac
False => dot
for c in s:
if c == ".":
if dot:
return []
True => dot
elif c >= "0" and c <= "9":
if dot:
frac + c => frac
else:
whole + c => whole
else:
return []
if whole == "" or len(frac) > 3 or (dot and frac == "") or len(whole) > 9:
return []
while len(frac) < 3:
frac + "0" => frac
return [sign * (int(whole) * 1000 + int(frac))]
def root_digits(n, k):
# floor(sqrt(n) x 10^k) for n >= 0, by the digit-pair method
str(n) => s
for i in [1:k]:
s + "00" => s
if len(s) % 2 == 1:
"0" + s => s
0 => p
0 => r
0 => i
while i < len(s):
r * 100 + int(s[i:i + 2]) => r
0 => d
while (20 * p + d + 1) * (d + 1) <= r:
d + 1 => d
r - (20 * p + d) * d => r
p * 10 + d => p
i + 2 => i
return p
def is_square(n):
root_digits(n, 0) => s
return s * s == n
def quotient_digits(num, den, k):
# floor(num / den x 10^k) for num >= 0, den > 0, by long division
str(num) => s
for i in [1:k]:
s + "0" => s
0 => q
0 => r
for c in s:
r * 10 + int(c) => r
0 => d
while r >= den:
r - den => r
d + 1 => d
q * 10 + d => q
return q
def scaled_text(n, k):
# n / 10^k as a decimal with k places
"" => sign
if n < 0:
"-" => sign
0 - n => n
str(n) => s
while len(s) <= k:
"0" + s => s
if k == 0:
return sign + s
return sign + s[0:len(s) - k] + "." + s[len(s) - k:len(s)]
def trimmed(t):
# drop trailing zeros after the point, and the point if nothing follows
t => s
False => dot
for c in s:
if c == ".":
True => dot
if not dot:
return s
while s[len(s) - 1] == "0":
s[0:len(s) - 1] => s
if s[len(s) - 1] == ".":
s[0:len(s) - 1] => s
return s
def abs_(n):
if n < 0:
return 0 - n
return n
def fraction_text(num, den):
# num / den reduced, the sign in front; den is not 0
if den < 0:
0 - num => num
0 - den => den
gcd(num, den) => g
if g == 0:
1 => g
quotient_digits(abs_(num), g, 0) => n
quotient_digits(den, g, 0) => d
"" => sign
if num < 0:
"-" => sign
if d == 1:
return sign + str(n)
return sign + str(n) + "/" + str(d)
def decimal12(top, root_sign, d, den):
# (top + root_sign x sqrt(d)) / den to 12 places, rounded half away from
# zero; den > 0 and d >= 0. Worked to 18 places first: the floor of the
# root is off by less than one unit there, far below the 12th place.
root_digits(d, 18) => s
"1" => t
for i in [1:18]:
t + "0" => t
top * int(t) + root_sign * s => num
"" => sign
if num < 0:
"-" => sign
0 - num => num
str(quotient_digits(num, den, 0)) => q
while len(q) < 7:
"0" + q => q
int(q[0:len(q) - 6]) => whole
if int(q[len(q) - 6:len(q)]) >= 500000:
whole + 1 => whole
if whole == 0:
"" => sign
return sign + scaled_text(whole, 12)
def solve(A, B, C):
# [kind, ...] for whole coefficients (already scaled):
# ["all"] or ["none"] when A = B = 0; ["linear", root]
# ["double", root]; ["two rational", root, root]
# ["two real", D, root, root] with D not a square, roots to 12 places
# ["complex", real part, imaginary part]
if A == 0:
if B == 0:
if C == 0:
return ["all"]
return ["none"]
return ["linear", fraction_text(0 - C, B)]
if A < 0:
# the same roots, with a positive leading coefficient
0 - A => A
0 - B => B
0 - C => C
B * B - 4 * A * C => D
2 * A => den
if D == 0:
return ["double", fraction_text(0 - B, den)]
if D > 0 and is_square(D):
root_digits(D, 0) => s
return ["two rational", fraction_text(0 - B - s, den), fraction_text(0 - B + s, den)]
if D > 0:
return ["two real", D, decimal12(0 - B, 0 - 1, D, den), decimal12(0 - B, 1, D, den)]
return ["complex", fraction_text(0 - B, den), decimal12(0, 1, 0 - D, den)]
def fsqrt(x):
# a float square root by Newton's method from above, using only
# adding, dividing and comparing - the same in every Python
if x <= 0.0:
return 0.0
x => r
if r < 1.0:
1.0 => r
for i in [1:200]:
(r + x / r) / 2.0 => y
if y >= r:
return r
y => r
return r
def small_root(b):
# x^2 + bx + 1 = 0 for whole b >= 3: [textbook, stable] in floats,
# the root nearer zero
b * 1.0 => fb
fsqrt(fb * fb - 4.0) => s
(0.0 - fb + s) / 2.0 => naive
(0.0 - fb - s) / 2.0 => big
1.0 / big => stable
return [naive, stable]
def exact_small(b, n):
# the root of x^2 + bx + 1 nearest zero is -2 / (b + sqrt(b^2 - 4));
# [its first n significant digits, the power of ten of the first one]
n + len(str(b)) + 5 => k
"1" => t
for i in [1:k]:
t + "0" => t
int(t) => ten
root_digits(b * b - 4, k) => s
str(quotient_digits(2 * ten * ten, b * ten + s, 0)) => q
return [q[0:n], len(q) - k - 1]
Python projection (quad.py)
def gcd(a, b):
if a < 0:
a = 0 - a
if b < 0:
b = 0 - b
while b != 0:
r = a % b
a = b
b = r
return a
def parse_coef(t):
if len(t) == 0:
return []
sign = 1
s = t
if s[0] == "-":
sign = 0 - 1
s = s[1:len(s)]
whole = ""
frac = ""
dot = False
for c in s:
if c == ".":
if dot:
return []
dot = True
elif c >= "0" and c <= "9":
if dot:
frac = frac + c
else:
whole = whole + c
else:
return []
if whole == "" or len(frac) > 3 or dot and frac == "" or len(whole) > 9:
return []
while len(frac) < 3:
frac = frac + "0"
return [sign * (int(whole) * 1000 + int(frac))]
def root_digits(n, k):
s = str(n)
for i in range(1, k+1):
s = s + "00"
if len(s) % 2 == 1:
s = "0" + s
p = 0
r = 0
i = 0
while i < len(s):
r = r * 100 + int(s[i:i + 2])
d = 0
while (20 * p + d + 1) * (d + 1) <= r:
d = d + 1
r = r - (20 * p + d) * d
p = p * 10 + d
i = i + 2
return p
def is_square(n):
s = root_digits(n, 0)
return s * s == n
def quotient_digits(num, den, k):
s = str(num)
for i in range(1, k+1):
s = s + "0"
q = 0
r = 0
for c in s:
r = r * 10 + int(c)
d = 0
while r >= den:
r = r - den
d = d + 1
q = q * 10 + d
return q
def scaled_text(n, k):
sign = ""
if n < 0:
sign = "-"
n = 0 - n
s = str(n)
while len(s) <= k:
s = "0" + s
if k == 0:
return sign + s
return sign + s[0:len(s) - k] + "." + s[len(s) - k:len(s)]
def trimmed(t):
s = t
dot = False
for c in s:
if c == ".":
dot = True
if not dot:
return s
while s[len(s) - 1] == "0":
s = s[0:len(s) - 1]
if s[len(s) - 1] == ".":
s = s[0:len(s) - 1]
return s
def abs_(n):
if n < 0:
return 0 - n
return n
def fraction_text(num, den):
if den < 0:
num = 0 - num
den = 0 - den
g = gcd(num, den)
if g == 0:
g = 1
n = quotient_digits(abs_(num), g, 0)
d = quotient_digits(den, g, 0)
sign = ""
if num < 0:
sign = "-"
if d == 1:
return sign + str(n)
return sign + str(n) + "/" + str(d)
def decimal12(top, root_sign, d, den):
s = root_digits(d, 18)
t = "1"
for i in range(1, 19):
t = t + "0"
num = top * int(t) + root_sign * s
sign = ""
if num < 0:
sign = "-"
num = 0 - num
q = str(quotient_digits(num, den, 0))
while len(q) < 7:
q = "0" + q
whole = int(q[0:len(q) - 6])
if int(q[len(q) - 6:len(q)]) >= 500000:
whole = whole + 1
if whole == 0:
sign = ""
return sign + scaled_text(whole, 12)
def solve(A, B, C):
if A == 0:
if B == 0:
if C == 0:
return ["all"]
return ["none"]
return ["linear", fraction_text(0 - C, B)]
if A < 0:
A = 0 - A
B = 0 - B
C = 0 - C
D = B * B - 4 * A * C
den = 2 * A
if D == 0:
return ["double", fraction_text(0 - B, den)]
if D > 0 and is_square(D):
s = root_digits(D, 0)
return ["two rational", fraction_text(0 - B - s, den), fraction_text(0 - B + s, den)]
if D > 0:
return ["two real", D, decimal12(0 - B, 0 - 1, D, den), decimal12(0 - B, 1, D, den)]
return ["complex", fraction_text(0 - B, den), decimal12(0, 1, 0 - D, den)]
def fsqrt(x):
if x <= 0.0:
return 0.0
r = x
if r < 1.0:
r = 1.0
for i in range(1, 201):
y = (r + x / r) / 2.0
if y >= r:
return r
r = y
return r
def small_root(b):
fb = b * 1.0
s = fsqrt(fb * fb - 4.0)
naive = (0.0 - fb + s) / 2.0
big = (0.0 - fb - s) / 2.0
stable = 1.0 / big
return [naive, stable]
def exact_small(b, n):
k = n + len(str(b)) + 5
t = "1"
for i in range(1, k+1):
t = t + "0"
ten = int(t)
s = root_digits(b * b - 4, k)
q = str(quotient_digits(2 * ten * ten, b * ten + s, 0))
return [q[0:n], len(q) - k - 1]