1#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum MathError {
10 Domain,
12 DivisionByZero,
14 InvalidArgument(&'static str),
16 NotConverged(&'static str),
18 DimensionMismatch,
20 Overflow,
22 Underflow,
24 Singular,
26 Timeout,
28 Io,
30 Parse,
32 NotImplemented,
34 OutOfRange,
36 NoSolution,
38 InfiniteSolutions,
40 InvalidBase,
42 NegativeInput,
44}
45
46pub type MathResult<T> = Result<T, MathError>;
48
49impl MathError {
50 #[must_use]
62 pub const fn msg(&self) -> &'static str {
63 match self {
64 Self::Domain => "input is outside the function domain",
65 Self::DivisionByZero => "division by zero",
66 Self::InvalidArgument(m) | Self::NotConverged(m) => m,
67 Self::DimensionMismatch => "dimension mismatch",
68 Self::Overflow => "numeric overflow",
69 Self::Underflow => "numeric underflow",
70 Self::Singular => "singular matrix",
71 Self::Timeout => "computation timed out",
72 Self::Io => "I/O error",
73 Self::Parse => "parse error",
74 Self::NotImplemented => "not implemented",
75 Self::OutOfRange => "value out of range",
76 Self::NoSolution => "no solution exists",
77 Self::InfiniteSolutions => "infinitely many solutions",
78 Self::InvalidBase => "invalid base",
79 Self::NegativeInput => "input must be non-negative",
80 }
81 }
82
83 #[must_use]
94 pub const fn has_context(&self) -> bool {
95 matches!(self, Self::InvalidArgument(_) | Self::NotConverged(_))
96 }
97}
98
99impl core::fmt::Display for MathError {
100 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
101 match self {
102 Self::InvalidArgument(msg) => write!(f, "invalid argument: {msg}"),
103 Self::NotConverged(method) => write!(f, "{method} failed to converge"),
104 other => f.write_str(other.msg()),
105 }
106 }
107}
108
109#[cfg(feature = "std")]
110impl std::error::Error for MathError {}
111
112#[allow(clippy::use_self)]
113impl From<MathError> for String {
114 fn from(e: MathError) -> String {
115 e.to_string()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn displays_with_context() {
125 assert_eq!(MathError::DivisionByZero.to_string(), "division by zero");
126 assert_eq!(
127 MathError::InvalidArgument("negative radius").to_string(),
128 "invalid argument: negative radius"
129 );
130 assert_eq!(MathError::Timeout.to_string(), "computation timed out");
131 assert_eq!(MathError::NoSolution.to_string(), "no solution exists");
132 assert_eq!(MathError::Parse.to_string(), "parse error");
133 }
134
135 #[test]
136 fn msg_method() {
137 assert_eq!(MathError::Domain.msg(), "input is outside the function domain");
138 assert_eq!(
139 MathError::InvalidArgument("test").msg(),
140 "test"
141 );
142 assert_eq!(MathError::Overflow.msg(), "numeric overflow");
143 }
144
145 #[test]
146 fn has_context_method() {
147 assert!(MathError::InvalidArgument("x").has_context());
148 assert!(MathError::NotConverged("newton").has_context());
149 assert!(!MathError::Domain.has_context());
150 assert!(!MathError::Overflow.has_context());
151 }
152
153 #[test]
154 fn into_string() {
155 let e: String = MathError::DivisionByZero.into();
156 assert_eq!(e, "division by zero");
157 }
158
159 #[test]
160 fn result_flow() {
161 fn invert(x: f64) -> MathResult<f64> {
162 if x == 0.0 {
163 Err(MathError::DivisionByZero)
164 } else {
165 Ok(1.0 / x)
166 }
167 }
168 assert_eq!(invert(4.0), Ok(0.25));
169 assert_eq!(invert(0.0), Err(MathError::DivisionByZero));
170 }
171}