2
\$\begingroup\$

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

mod quadratic;
use quadratic::QuadraticEquation;

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());
}

quadratic.rs

use std::fmt;

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

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

pub struct QuadraticEquation {
    a: f64,
    b: f64,
    c: f64,
}

impl QuadraticEquation {
    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(" ")
    }
}

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

quadratic/solution.rs

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())
    }
}

quadratic/functions.rs

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?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.