s5 0.1.0

Simple equation solver written in Rust, built for solving some homework-style problems in orbital mechanics
Documentation
//! Constants
//!
//! Represent integer and floating-point values/
use crate::display::{PrintUnit, PrintUnits, Printable};
use std::fmt::{Error, Formatter};
use std::ops;

#[derive(Copy, Clone, PartialOrd, PartialEq, Debug)]
enum EConst {
    IntConst(i64),

    FloatConst(f64),
}

impl Printable for EConst {
    fn to_print_units(&self) -> PrintUnits {
        let s = match self {
            EConst::IntConst(i) => i.to_string(),

            EConst::FloatConst(f) => f.to_string(),
        };

        PrintUnit::new(s.as_str()).into()
    }
}

/// Type for representing an integer or floating-point value.
#[derive(Copy, Clone, Debug)]
pub struct Const {
    c: EConst,
}

impl Const {
    /// Return true if self is an integer value equal to 1.
    pub fn is_one(&self) -> bool {
        match self.c {
            EConst::IntConst(n) => n == 1,
            EConst::FloatConst(_) => false,
        }
    }

    /// Return true if self is an integer value equal to -1.
    pub fn is_minus_one(&self) -> bool {
        match self.c {
            EConst::IntConst(n) => n == -1,
            EConst::FloatConst(_) => false,
        }
    }

    /// Return true if self is an integer value equal to 0.
    pub fn is_zero(&self) -> bool {
        match self.c {
            EConst::IntConst(n) => n == 0,
            EConst::FloatConst(_) => false,
        }
    }

    /// Return true if self is a finite value.
    pub fn is_finite(&self) -> bool {
        match self.c {
            EConst::IntConst(_) => true,
            EConst::FloatConst(f) => f.is_finite(),
        }
    }

    /// Convert to a raw floating-point value.
    pub fn to_f64(&self) -> f64 {
        match self.c {
            EConst::IntConst(n) => n as f64,
            EConst::FloatConst(f) => f,
        }
    }
}

impl Printable for Const {
    fn to_print_units(&self) -> PrintUnits {
        self.c.to_print_units()
    }
}

impl std::fmt::Display for Const {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        self.to_print_units().fmt(f)
    }
}

impl From<i64> for Const {
    fn from(n: i64) -> Self {
        Const {
            c: EConst::IntConst(n),
        }
    }
}

impl From<f64> for Const {
    fn from(f: f64) -> Self {
        Const {
            c: EConst::FloatConst(f),
        }
    }
}

impl From<Const> for f64 {
    fn from(c: Const) -> Self {
        match c.c {
            EConst::IntConst(i) => i as f64,
            EConst::FloatConst(f) => f,
        }
    }
}

// helper macro for overloading operators between `Const` and other types
macro_rules! const_op {
    ($($trait:ident)::+, $fn:ident) => {
        impl $($trait)::+<Const> for Const {
            type Output = Const;

            fn $fn(self, rhs: Const) -> Self::Output {
                match (self.c, rhs.c) {
                    (EConst::IntConst(i1), EConst::IntConst(i2)) => {
                        Const {
                            c: EConst::IntConst(i1.$fn(i2)),
                        }
                    },

                    (EConst::IntConst(i), EConst::FloatConst(f)) => {
                        Const {
                            c: EConst::FloatConst((i as f64).$fn(f)),
                        }
                    },

                    (EConst::FloatConst(f), EConst::IntConst(i)) => {
                        Const {
                            c: EConst::FloatConst(f.$fn(i as f64)),
                        }
                    },

                    (EConst::FloatConst(f1), EConst::FloatConst(f2)) => {
                        Const {
                            c: EConst::FloatConst(f1.$fn(f2)),
                        }
                    },
                }
            }
        }

        impl $($trait)::+<i64> for Const {
            type Output = Const;

            fn $fn(self, rhs: i64) -> Self::Output {
                self.$fn(Const::from(rhs))
            }
        }

        impl $($trait)::+<Const> for i64 {
            type Output = Const;

            fn $fn(self, rhs: Const) -> Self::Output {
                Const::from(self).$fn(rhs)
            }
        }

        impl $($trait)::+<f64> for Const {
            type Output = Const;

            fn $fn(self, rhs: f64) -> Const {
                self.$fn(Const::from(rhs))
            }
        }

        impl $($trait)::+<Const> for f64 {
            type Output = Const;

            fn $fn(self, rhs: Const) -> Const {
                Const::from(self).$fn(rhs)
            }
        }
    };
}

// Overload operators between `Const` and other types.
const_op!(ops::Add, add);
const_op!(ops::Sub, sub);
const_op!(ops::Mul, mul);

// Implement division between `Const` and other types manually due to some extra logic.
impl ops::Div<Const> for Const {
    type Output = Const;

    fn div(self, rhs: Const) -> Self::Output {
        match (self.c, rhs.c) {
            (EConst::IntConst(i1), EConst::IntConst(i2)) => Const {
                // The aforementioned special logic: when dividing integers, we want a floating-
                // point value. Mathematically, `1/2 != 0`!
                c: EConst::FloatConst((i1 as f64).div(i2 as f64)),
            },

            (EConst::IntConst(i), EConst::FloatConst(f)) => Const {
                c: EConst::FloatConst((i as f64).div(f)),
            },

            (EConst::FloatConst(f), EConst::IntConst(i)) => Const {
                c: EConst::FloatConst(f.div(i as f64)),
            },

            (EConst::FloatConst(f1), EConst::FloatConst(f2)) => Const {
                c: EConst::FloatConst(f1.div(f2)),
            },
        }
    }
}

impl ops::Div<i64> for Const {
    type Output = Const;

    fn div(self, rhs: i64) -> Self::Output {
        self.div(Const::from(rhs))
    }
}

impl ops::Div<Const> for i64 {
    type Output = Const;

    fn div(self, rhs: Const) -> Self::Output {
        Const::from(self).div(rhs)
    }
}

impl ops::Div<f64> for Const {
    type Output = Const;

    fn div(self, rhs: f64) -> Const {
        self.div(Const::from(rhs))
    }
}

impl ops::Div<Const> for f64 {
    type Output = Const;

    fn div(self, rhs: Const) -> Const {
        Const::from(self).div(rhs)
    }
}

// Using the ^ for exponents
// Implement exponentiation manually because we need to use different exponentiation methods
// depending on the types (int or float) involved.
impl ops::BitXor<i64> for Const {
    type Output = Const;

    fn bitxor(self, rhs: i64) -> Self::Output {
        match self.c {
            EConst::IntConst(i) => {
                if i < 0 {
                    Const {
                        c: EConst::FloatConst((i as f64).powi(rhs as i32)),
                    }
                } else {
                    Const {
                        c: EConst::IntConst(i.pow(rhs as u32)),
                    }
                }
            }

            EConst::FloatConst(f) => Const {
                c: EConst::FloatConst(f.powi(rhs as i32)),
            },
        }
    }
}

impl ops::BitXor<Const> for i64 {
    type Output = Const;

    fn bitxor(self, rhs: Const) -> Self::Output {
        match rhs.c {
            EConst::IntConst(i) => {
                if i < 0 {
                    Const {
                        c: EConst::FloatConst((self as f64).powi(i as i32)),
                    }
                } else {
                    Const {
                        c: EConst::IntConst(self.pow(i as u32)),
                    }
                }
            }

            EConst::FloatConst(f) => Const {
                c: EConst::FloatConst((self as f64).powf(f)),
            },
        }
    }
}

impl ops::BitXor<Const> for Const {
    type Output = Const;

    fn bitxor(self, rhs: Const) -> Self::Output {
        match (self.c, rhs.c) {
            (EConst::IntConst(i1), EConst::IntConst(i2)) => {
                if i2 < 0 {
                    Const {
                        c: EConst::FloatConst((i1 as f64).powi(i2 as i32)),
                    }
                } else {
                    Const {
                        c: EConst::IntConst(i1.pow(i2 as u32)),
                    }
                }
            }

            (EConst::IntConst(i), EConst::FloatConst(f)) => Const {
                c: EConst::FloatConst((i as f64).powf(f)),
            },

            (EConst::FloatConst(f), EConst::IntConst(i)) => Const {
                c: EConst::FloatConst(f.powf(i as f64)),
            },

            (EConst::FloatConst(f1), EConst::FloatConst(f2)) => Const {
                c: EConst::FloatConst(f1.powf(f2)),
            },
        }
    }
}

impl ops::BitXor<f64> for Const {
    type Output = Const;

    fn bitxor(self, rhs: f64) -> Const {
        match self.c {
            EConst::IntConst(i) => Const {
                c: EConst::FloatConst((i as f64).powf(rhs)),
            },

            EConst::FloatConst(f) => Const {
                c: EConst::FloatConst(f.powf(rhs)),
            },
        }
    }
}

impl ops::BitXor<Const> for f64 {
    type Output = Const;

    fn bitxor(self, rhs: Const) -> Const {
        match rhs.c {
            EConst::IntConst(i) => Const {
                c: EConst::FloatConst(self.powf(i as f64)),
            },

            EConst::FloatConst(f) => Const {
                c: EConst::FloatConst(self.powf(f)),
            },
        }
    }
}