This commit is contained in:
workD12
2026-07-31 20:16:28 +03:00
commit 8e24f19a07
20 changed files with 10037 additions and 0 deletions
+425
View File
@@ -0,0 +1,425 @@
//! 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<f64, CalcError> {
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<f64>,
/// Pending binary operator.
op: Option<Op>,
/// Expression line above the main display (e.g. "12 +").
expression: String,
/// Last error, if any.
error: Option<CalcError>,
/// 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<f64, CalcError>) {
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<f64, CalcError> {
self.entry
.parse::<f64>()
.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"));
}
}