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