//! Calculator logic (standard / Windows-like). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Op { Add, Sub, Mul, Div, } impl Op { pub fn symbol(self) -> &'static str { match self { Op::Add => "+", Op::Sub => "−", Op::Mul => "×", Op::Div => "÷", } } fn apply(self, a: f64, b: f64) -> Result { let r = match self { Op::Add => a + b, Op::Sub => a - b, Op::Mul => a * b, Op::Div => { if b == 0.0 { return Err(CalcError::DivByZero); } a / b } }; if !r.is_finite() { Err(CalcError::Overflow) } else { Ok(r) } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CalcError { DivByZero, Overflow, Invalid, } impl CalcError { pub fn message(self) -> &'static str { match self { CalcError::DivByZero => "Cannot divide by zero", CalcError::Overflow => "Overflow", CalcError::Invalid => "Invalid input", } } } #[derive(Debug, Clone)] pub struct Calculator { /// Digits currently being entered (or last result as string). entry: String, /// True while the user is typing a new number. typing: bool, /// Left operand waiting for the next number / equals. pending: Option, /// Pending binary operator. op: Option, /// Expression line above the main display (e.g. "12 +"). expression: String, /// Last error, if any. error: Option, /// Memory register. memory: f64, /// Whether memory has been set (for UI hint). memory_set: bool, } impl Default for Calculator { fn default() -> Self { Self::new() } } impl Calculator { pub fn new() -> Self { Self { entry: "0".into(), typing: false, pending: None, op: None, expression: String::new(), error: None, memory: 0.0, memory_set: false, } } pub fn display(&self) -> &str { if let Some(err) = self.error { return err.message(); } &self.entry } pub fn expression(&self) -> &str { &self.expression } pub fn has_memory(&self) -> bool { self.memory_set } pub fn clear_all(&mut self) { *self = Self { memory: self.memory, memory_set: self.memory_set, ..Self::new() }; } pub fn clear_entry(&mut self) { self.error = None; self.entry = "0".into(); self.typing = false; } pub fn backspace(&mut self) { if self.error.is_some() { self.clear_entry(); return; } if !self.typing { return; } self.entry.pop(); if self.entry.is_empty() || self.entry == "-" { self.entry = "0".into(); self.typing = false; } } pub fn input_digit(&mut self, d: char) { debug_assert!(d.is_ascii_digit()); if self.error.is_some() { self.clear_all(); } if !self.typing { self.entry = d.to_string(); self.typing = true; return; } if self.entry == "0" { self.entry = d.to_string(); } else if self.entry == "-0" { self.entry = format!("-{d}"); } else if digit_count(&self.entry) < 16 { self.entry.push(d); } } pub fn input_dot(&mut self) { if self.error.is_some() { self.clear_all(); } if !self.typing { self.entry = "0.".into(); self.typing = true; return; } if !self.entry.contains('.') { self.entry.push('.'); } } pub fn negate(&mut self) { if self.error.is_some() { return; } if let Ok(v) = self.current_value() { self.set_entry(-v); self.typing = true; } } pub fn percent(&mut self) { if self.error.is_some() { return; } let Ok(cur) = self.current_value() else { return; }; let result = match (self.pending, self.op) { (Some(a), Some(Op::Add | Op::Sub)) => a * cur / 100.0, _ => cur / 100.0, }; self.set_entry(result); self.typing = false; } pub fn reciprocal(&mut self) { self.unary(|x| { if x == 0.0 { Err(CalcError::DivByZero) } else { Ok(1.0 / x) } }); } pub fn square(&mut self) { self.unary(|x| { let r = x * x; if r.is_finite() { Ok(r) } else { Err(CalcError::Overflow) } }); } pub fn sqrt(&mut self) { self.unary(|x| { if x < 0.0 { Err(CalcError::Invalid) } else { Ok(x.sqrt()) } }); } pub fn set_op(&mut self, op: Op) { if self.error.is_some() { return; } if let Err(e) = self.commit_pending() { self.fail(e); return; } let Ok(v) = self.current_value() else { return; }; self.pending = Some(v); self.op = Some(op); self.expression = format!("{} {}", format_number(v), op.symbol()); self.typing = false; } pub fn equals(&mut self) { if self.error.is_some() { return; } let Some(op) = self.op else { self.expression.clear(); self.typing = false; return; }; let Ok(b) = self.current_value() else { return; }; let a = self.pending.unwrap_or(b); match op.apply(a, b) { Ok(r) => { self.expression = format!( "{} {} {} =", format_number(a), op.symbol(), format_number(b) ); self.set_entry(r); self.pending = None; self.op = None; self.typing = false; } Err(e) => self.fail(e), } } // --- Memory --- pub fn memory_clear(&mut self) { self.memory = 0.0; self.memory_set = false; } pub fn memory_recall(&mut self) { if self.error.is_some() { self.clear_all(); } self.set_entry(self.memory); self.typing = false; } pub fn memory_add(&mut self) { if self.error.is_some() { return; } if let Ok(v) = self.current_value() { self.memory += v; self.memory_set = true; self.typing = false; } } pub fn memory_sub(&mut self) { if self.error.is_some() { return; } if let Ok(v) = self.current_value() { self.memory -= v; self.memory_set = true; self.typing = false; } } pub fn memory_store(&mut self) { if self.error.is_some() { return; } if let Ok(v) = self.current_value() { self.memory = v; self.memory_set = true; self.typing = false; } } // --- internals --- fn unary(&mut self, f: impl FnOnce(f64) -> Result) { if self.error.is_some() { return; } let Ok(v) = self.current_value() else { return; }; match f(v) { Ok(r) => { self.expression.clear(); self.set_entry(r); self.typing = false; } Err(e) => self.fail(e), } } fn commit_pending(&mut self) -> Result<(), CalcError> { let (Some(a), Some(op)) = (self.pending, self.op) else { return Ok(()); }; if !self.typing { return Ok(()); } let b = self.current_value()?; let r = op.apply(a, b)?; self.set_entry(r); self.pending = Some(r); self.op = None; Ok(()) } fn current_value(&self) -> Result { self.entry .parse::() .map_err(|_| CalcError::Invalid) } fn set_entry(&mut self, v: f64) { self.entry = format_number(v); self.error = None; } fn fail(&mut self, e: CalcError) { self.error = Some(e); self.pending = None; self.op = None; self.expression.clear(); self.typing = false; } } fn digit_count(s: &str) -> usize { s.chars().filter(|c| c.is_ascii_digit()).count() } fn format_number(v: f64) -> String { if !v.is_finite() { return "0".into(); } // Prefer integer display when close enough. if v.fract().abs() < 1e-12 && v.abs() < 1e15 { return format!("{}", v as i64); } let s = format!("{:.12}", v); let s = s.trim_end_matches('0').trim_end_matches('.').to_string(); if s.is_empty() || s == "-" { "0".into() } else { s } } #[cfg(test)] mod tests { use super::*; #[test] fn basic_add() { let mut c = Calculator::new(); c.input_digit('1'); c.input_digit('2'); c.set_op(Op::Add); c.input_digit('3'); c.equals(); assert_eq!(c.display(), "15"); } #[test] fn div_by_zero() { let mut c = Calculator::new(); c.input_digit('1'); c.set_op(Op::Div); c.input_digit('0'); c.equals(); assert!(c.display().contains("zero")); } }