# Quadratic equation solver using ABC and PQ formula

Getting my feet wet with Rust, I implemented a solver for quadratic equations. I implemented both, ABC and PQ formula solvers, to challenge myself with branch conditions.

main.rs

fn main() {
let equation1 = QuadraticEquation::new(3.0, 3.0, -18.0);
let equation2 = QuadraticEquation::new(1.0, 3.0, -18.0);
let equation3 = QuadraticEquation::new(-4.0, -5.0, 12.0);
let equation4 = QuadraticEquation::new(1.0, 0.0, 50.0);
let equation5 = QuadraticEquation::new(1.0, 0.0, 1.0);
println!("The solutions of {} are {}", equation1, equation1.solve());
println!("The solutions of {} are {}", equation2, equation2.solve());
println!("The solutions of {} are {}", equation3, equation3.solve());
println!("The solutions of {} are {}", equation4, equation4.solve());
println!("The solutions of {} are {}", equation5, equation5.solve());
}

use std::fmt;

mod solution;
pub use self::solution::Solution;

mod functions;
use self::functions::with_sign;

a: f64,
b: f64,
c: f64,
}

pub fn new(a: f64, b: f64, c: f64) -> QuadraticEquation {
QuadraticEquation { a: a, b: b, c: c }
}

pub fn solve_abc(&self) -> Solution {
let root = f64::sqrt(f64::powi(self.b, 2) - 4.0 * self.a * self.c);
let x1 = (-self.b + root) / (2f64 * self.a);
let x2 = (-self.b - root) / (2f64 * self.a);
Solution::new(x1, x2)
}

pub fn solve_pq(&self) -> Solution {
if self.a != 1.0 {
return Solution::none();
}

let minus_b_half = -self.b / 2.0;
let root = f64::sqrt(f64::powi(self.b / 2.0, 2) - self.c);
let x1 = minus_b_half + root;
let x2 = minus_b_half - root;
Solution::new(x1, x2)
}

pub fn solve(&self) -> Solution {
if self.a == 1.0 {
println!("a = 1 -> Using pq-formula.");
self.solve_pq()
} else {
self.solve_abc()
}
}

pub fn to_string(&self) -> String {
let mut result = Vec::new();

if self.a != 0.0 {
result.push(format!("{}x²", with_sign(self.a, true, result.is_empty())));
}

if self.b != 0.0 {
result.push(format!("{}x", with_sign(self.b, true, result.is_empty())));
}

if self.c != 0.0 {
result.push(with_sign(self.c, false, result.is_empty()));
}

result.join(" ")
}
}

fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}

use std::fmt;

pub struct Solution {
pub x1: f64,
pub x2: f64,
}

impl Solution {
pub fn new(x1: f64, x2: f64) -> Solution {
Solution { x1: x1, x2: x2 }
}

pub fn none() -> Solution {
Solution::new(f64::NAN, f64::NAN)
}

pub fn error(&self) -> bool {
self.x1.is_nan() && self.x2.is_nan()
}

pub fn to_string(&self) -> String {
if self.error() {
"N/A".to_string()
} else {
format!("x₁ = {}, x₂ = {}", self.x1, self.x2)
}
}
}

impl fmt::Display for Solution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_string())
}
}

pub fn with_sign(number: f64, omit_one: bool, first: bool) -> String {
if number < 0.0 {
with_minus(abs_str(number, omit_one), first)
} else {
with_plus(abs_str(number, omit_one), first)
}
}

fn abs_str(number: f64, omit_one: bool) -> String {
if omit_one {
empty_if_one(number.abs())
} else {
number.abs().to_string()
}
}

fn with_minus(number: String, first: bool) -> String {
if first {
format!("-{}", number)
} else {
format!("- {}", number)
}
}

fn with_plus(number: String, first: bool) -> String {
if first {
number
} else {
format!("+ {}", number)
}
}

fn empty_if_one(number: f64) -> String {
if number == 1.0 {
"".to_string()
} else {
number.to_string()
}
}

How can I improve the code?

For one, the special case of a=1 is not very interesting. You could as well remove the branch and just use abc everytime.

Instead you could check if a is close to 0 and choose a different formula that is more numerically stable in this case.

Second I dislike the flag arguments a little:

with_sign(number: f64, omit_one: bool, first: bool)

They make the call site cryptic, I can hardly see why the arguments are passed that way. I would replace this with 2 separate functions: One for the sign/operator and one for the coefficient:

if self.a != 0.0 {
let withSpace = result.any(); // Not sure if this works in Rust
result.push(format_sign(self.a, withSpace))
result.push(format!("{}x²", format_coefficient(self.a)));
}
//...
if self.c != 0.0 {
let withSpace = result.any(); // Not sure if this works in Rust
result.push(format_sign(self.a, withSpace))
result.push(format_constant(self.c));
}

So format_sign would print a + or - and with or without the space and then format_constant will just print the number and format_coefficient can return "" if the number is exactly one.

Sorry, I don't have a rust compiler at hand.