Skip to main content

showcase_calculator/core/
operations.rs

1//! Core calculator operations with 100% test coverage
2//!
3//! Probar: Error prevention - Type-safe operations prevent invalid states
4
5use crate::core::{AnomalyValidator, CalcError, CalcResult};
6
7/// Type-safe operation enum - compile-time guarantee of valid operations
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Operation {
10    /// Addition (+)
11    Add,
12    /// Subtraction (-)
13    Subtract,
14    /// Multiplication (*)
15    Multiply,
16    /// Division (/)
17    Divide,
18    /// Modulo (%)
19    Modulo,
20    /// Power (^)
21    Power,
22}
23
24impl Operation {
25    /// Returns the operator symbol for display
26    #[must_use]
27    pub const fn symbol(&self) -> &'static str {
28        match self {
29            Self::Add => "+",
30            Self::Subtract => "-",
31            Self::Multiply => "*",
32            Self::Divide => "/",
33            Self::Modulo => "%",
34            Self::Power => "^",
35        }
36    }
37
38    /// Returns the precedence level for operator ordering (higher = evaluated first)
39    #[must_use]
40    pub const fn precedence(&self) -> u8 {
41        match self {
42            Self::Add | Self::Subtract => 1,
43            Self::Multiply | Self::Divide | Self::Modulo => 2,
44            Self::Power => 3,
45        }
46    }
47
48    /// Returns true if this operation is left-associative
49    #[must_use]
50    pub const fn is_left_associative(&self) -> bool {
51        !matches!(self, Self::Power)
52    }
53}
54
55/// Core calculator implementing all arithmetic operations
56#[derive(Debug, Default)]
57pub struct Calculator {
58    /// Anomaly validator for anomaly detection
59    pub(crate) validator: AnomalyValidator,
60}
61
62impl Calculator {
63    /// Creates a new calculator with default settings
64    #[must_use]
65    pub fn new() -> Self {
66        Self {
67            validator: AnomalyValidator::new(),
68        }
69    }
70
71    /// Creates a calculator with custom Anomaly validator
72    #[must_use]
73    pub fn with_validator(validator: AnomalyValidator) -> Self {
74        Self { validator }
75    }
76
77    /// Performs an operation on two operands
78    pub fn calculate(&mut self, a: f64, b: f64, op: Operation) -> CalcResult<f64> {
79        let raw_result = match op {
80            Operation::Add => Self::add(a, b)?,
81            Operation::Subtract => Self::subtract(a, b)?,
82            Operation::Multiply => Self::multiply(a, b)?,
83            Operation::Divide => Self::divide(a, b)?,
84            Operation::Modulo => Self::modulo(a, b)?,
85            Operation::Power => Self::power(a, b)?,
86        };
87
88        // Anomaly: Validate result
89        self.validator
90            .validate(raw_result)
91            .map_err(CalcError::AnomalyViolation)
92    }
93
94    /// Addition: a + b
95    pub fn add(a: f64, b: f64) -> CalcResult<f64> {
96        let result = a + b;
97        Self::check_overflow(result)
98    }
99
100    /// Subtraction: a - b
101    pub fn subtract(a: f64, b: f64) -> CalcResult<f64> {
102        let result = a - b;
103        Self::check_overflow(result)
104    }
105
106    /// Multiplication: a * b
107    pub fn multiply(a: f64, b: f64) -> CalcResult<f64> {
108        let result = a * b;
109        Self::check_overflow(result)
110    }
111
112    /// Division: a / b
113    pub fn divide(a: f64, b: f64) -> CalcResult<f64> {
114        if b == 0.0 {
115            return Err(CalcError::DivisionByZero);
116        }
117        let result = a / b;
118        Self::check_overflow(result)
119    }
120
121    /// Modulo: a % b
122    pub fn modulo(a: f64, b: f64) -> CalcResult<f64> {
123        if b == 0.0 {
124            return Err(CalcError::DivisionByZero);
125        }
126        let result = a % b;
127        Self::check_overflow(result)
128    }
129
130    /// Power: a ^ b
131    pub fn power(a: f64, b: f64) -> CalcResult<f64> {
132        let result = a.powf(b);
133        Self::check_overflow(result)
134    }
135
136    /// Checks for overflow (infinity or NaN)
137    fn check_overflow(result: f64) -> CalcResult<f64> {
138        if result.is_nan() {
139            Err(CalcError::InvalidResult("NaN".into()))
140        } else if result.is_infinite() {
141            Err(CalcError::Overflow)
142        } else {
143            Ok(result)
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::core::AnomalyViolation;
152    use proptest::prelude::*;
153
154    // ===== EXTREME TDD: Tests written FIRST =====
155
156    // --- Operation enum tests ---
157
158    #[test]
159    fn test_operation_symbol_add() {
160        assert_eq!(Operation::Add.symbol(), "+");
161    }
162
163    #[test]
164    fn test_operation_symbol_subtract() {
165        assert_eq!(Operation::Subtract.symbol(), "-");
166    }
167
168    #[test]
169    fn test_operation_symbol_multiply() {
170        assert_eq!(Operation::Multiply.symbol(), "*");
171    }
172
173    #[test]
174    fn test_operation_symbol_divide() {
175        assert_eq!(Operation::Divide.symbol(), "/");
176    }
177
178    #[test]
179    fn test_operation_symbol_modulo() {
180        assert_eq!(Operation::Modulo.symbol(), "%");
181    }
182
183    #[test]
184    fn test_operation_symbol_power() {
185        assert_eq!(Operation::Power.symbol(), "^");
186    }
187
188    #[test]
189    fn test_operation_precedence_add_subtract() {
190        assert_eq!(Operation::Add.precedence(), 1);
191        assert_eq!(Operation::Subtract.precedence(), 1);
192    }
193
194    #[test]
195    fn test_operation_precedence_mul_div_mod() {
196        assert_eq!(Operation::Multiply.precedence(), 2);
197        assert_eq!(Operation::Divide.precedence(), 2);
198        assert_eq!(Operation::Modulo.precedence(), 2);
199    }
200
201    #[test]
202    fn test_operation_precedence_power() {
203        assert_eq!(Operation::Power.precedence(), 3);
204    }
205
206    #[test]
207    fn test_operation_associativity() {
208        assert!(Operation::Add.is_left_associative());
209        assert!(Operation::Subtract.is_left_associative());
210        assert!(Operation::Multiply.is_left_associative());
211        assert!(Operation::Divide.is_left_associative());
212        assert!(Operation::Modulo.is_left_associative());
213        assert!(!Operation::Power.is_left_associative());
214    }
215
216    // --- Calculator creation tests ---
217
218    #[test]
219    fn test_calculator_new() {
220        let calc = Calculator::new();
221        assert!(calc.validator.max_magnitude > 0.0);
222    }
223
224    #[test]
225    fn test_calculator_with_validator() {
226        let validator = AnomalyValidator::with_max_magnitude(100.0);
227        let calc = Calculator::with_validator(validator);
228        assert_eq!(calc.validator.max_magnitude, 100.0);
229    }
230
231    // --- Addition tests ---
232
233    #[test]
234    fn test_add_positive_numbers() {
235        assert_eq!(Calculator::add(2.0, 3.0), Ok(5.0));
236    }
237
238    #[test]
239    fn test_add_negative_numbers() {
240        assert_eq!(Calculator::add(-2.0, -3.0), Ok(-5.0));
241    }
242
243    #[test]
244    fn test_add_mixed_numbers() {
245        assert_eq!(Calculator::add(-2.0, 5.0), Ok(3.0));
246    }
247
248    #[test]
249    fn test_add_zero() {
250        assert_eq!(Calculator::add(5.0, 0.0), Ok(5.0));
251        assert_eq!(Calculator::add(0.0, 5.0), Ok(5.0));
252    }
253
254    #[test]
255    fn test_add_decimals() {
256        let result = Calculator::add(0.1, 0.2).unwrap();
257        assert!((result - 0.3).abs() < 1e-10);
258    }
259
260    // --- Subtraction tests ---
261
262    #[test]
263    fn test_subtract_positive_numbers() {
264        assert_eq!(Calculator::subtract(5.0, 3.0), Ok(2.0));
265    }
266
267    #[test]
268    fn test_subtract_negative_numbers() {
269        assert_eq!(Calculator::subtract(-2.0, -3.0), Ok(1.0));
270    }
271
272    #[test]
273    fn test_subtract_to_negative() {
274        assert_eq!(Calculator::subtract(3.0, 5.0), Ok(-2.0));
275    }
276
277    #[test]
278    fn test_subtract_zero() {
279        assert_eq!(Calculator::subtract(5.0, 0.0), Ok(5.0));
280    }
281
282    // --- Multiplication tests ---
283
284    #[test]
285    fn test_multiply_positive_numbers() {
286        assert_eq!(Calculator::multiply(2.0, 3.0), Ok(6.0));
287    }
288
289    #[test]
290    fn test_multiply_negative_numbers() {
291        assert_eq!(Calculator::multiply(-2.0, -3.0), Ok(6.0));
292    }
293
294    #[test]
295    fn test_multiply_mixed_signs() {
296        assert_eq!(Calculator::multiply(-2.0, 3.0), Ok(-6.0));
297    }
298
299    #[test]
300    fn test_multiply_by_zero() {
301        assert_eq!(Calculator::multiply(5.0, 0.0), Ok(0.0));
302        assert_eq!(Calculator::multiply(0.0, 5.0), Ok(0.0));
303    }
304
305    #[test]
306    fn test_multiply_by_one() {
307        assert_eq!(Calculator::multiply(5.0, 1.0), Ok(5.0));
308        assert_eq!(Calculator::multiply(1.0, 5.0), Ok(5.0));
309    }
310
311    // --- Division tests ---
312
313    #[test]
314    fn test_divide_positive_numbers() {
315        assert_eq!(Calculator::divide(6.0, 2.0), Ok(3.0));
316    }
317
318    #[test]
319    fn test_divide_by_zero() {
320        assert_eq!(
321            Calculator::divide(10.0, 0.0),
322            Err(CalcError::DivisionByZero)
323        );
324    }
325
326    #[test]
327    fn test_divide_negative_numbers() {
328        assert_eq!(Calculator::divide(-6.0, -2.0), Ok(3.0));
329    }
330
331    #[test]
332    fn test_divide_mixed_signs() {
333        assert_eq!(Calculator::divide(-6.0, 2.0), Ok(-3.0));
334    }
335
336    #[test]
337    fn test_divide_by_one() {
338        assert_eq!(Calculator::divide(5.0, 1.0), Ok(5.0));
339    }
340
341    #[test]
342    fn test_divide_zero_by_number() {
343        assert_eq!(Calculator::divide(0.0, 5.0), Ok(0.0));
344    }
345
346    // --- Modulo tests ---
347
348    #[test]
349    fn test_modulo_positive_numbers() {
350        assert_eq!(Calculator::modulo(7.0, 3.0), Ok(1.0));
351    }
352
353    #[test]
354    fn test_modulo_by_zero() {
355        assert_eq!(
356            Calculator::modulo(10.0, 0.0),
357            Err(CalcError::DivisionByZero)
358        );
359    }
360
361    #[test]
362    fn test_modulo_no_remainder() {
363        assert_eq!(Calculator::modulo(6.0, 3.0), Ok(0.0));
364    }
365
366    #[test]
367    fn test_modulo_negative_dividend() {
368        let result = Calculator::modulo(-7.0, 3.0).unwrap();
369        assert!((result - -1.0).abs() < 1e-10);
370    }
371
372    // --- Power tests ---
373
374    #[test]
375    fn test_power_positive_integers() {
376        assert_eq!(Calculator::power(2.0, 3.0), Ok(8.0));
377    }
378
379    #[test]
380    fn test_power_zero_exponent() {
381        assert_eq!(Calculator::power(5.0, 0.0), Ok(1.0));
382    }
383
384    #[test]
385    fn test_power_one_exponent() {
386        assert_eq!(Calculator::power(5.0, 1.0), Ok(5.0));
387    }
388
389    #[test]
390    fn test_power_negative_exponent() {
391        assert_eq!(Calculator::power(2.0, -1.0), Ok(0.5));
392    }
393
394    #[test]
395    fn test_power_fractional_exponent() {
396        let result = Calculator::power(4.0, 0.5).unwrap();
397        assert!((result - 2.0).abs() < 1e-10);
398    }
399
400    #[test]
401    fn test_power_negative_base_integer_exp() {
402        assert_eq!(Calculator::power(-2.0, 2.0), Ok(4.0));
403        assert_eq!(Calculator::power(-2.0, 3.0), Ok(-8.0));
404    }
405
406    #[test]
407    fn test_power_overflow() {
408        assert_eq!(Calculator::power(10.0, 1000.0), Err(CalcError::Overflow));
409    }
410
411    #[test]
412    fn test_power_negative_base_fractional_exp_nan() {
413        // (-2)^0.5 is NaN
414        let result = Calculator::power(-2.0, 0.5);
415        assert!(matches!(result, Err(CalcError::InvalidResult(_))));
416    }
417
418    // --- Calculator.calculate() integration tests ---
419
420    #[test]
421    fn test_calculate_add() {
422        let mut calc = Calculator::new();
423        assert_eq!(calc.calculate(2.0, 3.0, Operation::Add), Ok(5.0));
424    }
425
426    #[test]
427    fn test_calculate_subtract() {
428        let mut calc = Calculator::new();
429        assert_eq!(calc.calculate(5.0, 3.0, Operation::Subtract), Ok(2.0));
430    }
431
432    #[test]
433    fn test_calculate_multiply() {
434        let mut calc = Calculator::new();
435        assert_eq!(calc.calculate(4.0, 3.0, Operation::Multiply), Ok(12.0));
436    }
437
438    #[test]
439    fn test_calculate_divide() {
440        let mut calc = Calculator::new();
441        assert_eq!(calc.calculate(12.0, 4.0, Operation::Divide), Ok(3.0));
442    }
443
444    #[test]
445    fn test_calculate_modulo() {
446        let mut calc = Calculator::new();
447        assert_eq!(calc.calculate(7.0, 3.0, Operation::Modulo), Ok(1.0));
448    }
449
450    #[test]
451    fn test_calculate_power() {
452        let mut calc = Calculator::new();
453        assert_eq!(calc.calculate(2.0, 3.0, Operation::Power), Ok(8.0));
454    }
455
456    #[test]
457    fn test_calculate_with_jidoka_overflow() {
458        let validator = AnomalyValidator::with_max_magnitude(100.0);
459        let mut calc = Calculator::with_validator(validator);
460        let result = calc.calculate(50.0, 3.0, Operation::Multiply);
461        assert!(matches!(
462            result,
463            Err(CalcError::AnomalyViolation(AnomalyViolation::Overflow(_)))
464        ));
465    }
466
467    // --- Property-based tests ---
468
469    proptest! {
470        #[test]
471        fn prop_add_commutative(a in -1e10f64..1e10f64, b in -1e10f64..1e10f64) {
472            prop_assume!(!a.is_nan() && !b.is_nan());
473            let r1 = Calculator::add(a, b);
474            let r2 = Calculator::add(b, a);
475            match (r1, r2) {
476                (Ok(v1), Ok(v2)) => prop_assert!((v1 - v2).abs() < 1e-10),
477                (Err(_), Err(_)) => {}
478                _ => prop_assert!(false, "Commutativity violated"),
479            }
480        }
481
482        #[test]
483        fn prop_multiply_commutative(a in -1e5f64..1e5f64, b in -1e5f64..1e5f64) {
484            prop_assume!(!a.is_nan() && !b.is_nan());
485            let r1 = Calculator::multiply(a, b);
486            let r2 = Calculator::multiply(b, a);
487            match (r1, r2) {
488                (Ok(v1), Ok(v2)) => prop_assert!((v1 - v2).abs() < 1e-10),
489                (Err(_), Err(_)) => {}
490                _ => prop_assert!(false, "Commutativity violated"),
491            }
492        }
493
494        #[test]
495        fn prop_add_identity(a in -1e10f64..1e10f64) {
496            prop_assume!(!a.is_nan());
497            let result = Calculator::add(a, 0.0);
498            prop_assert_eq!(result, Ok(a));
499        }
500
501        #[test]
502        fn prop_multiply_identity(a in -1e10f64..1e10f64) {
503            prop_assume!(!a.is_nan());
504            let result = Calculator::multiply(a, 1.0);
505            prop_assert_eq!(result, Ok(a));
506        }
507
508        #[test]
509        fn prop_multiply_zero(a in -1e10f64..1e10f64) {
510            prop_assume!(!a.is_nan());
511            let result = Calculator::multiply(a, 0.0);
512            prop_assert_eq!(result, Ok(0.0));
513        }
514
515        #[test]
516        fn prop_divide_by_self(a in -1e10f64..1e10f64) {
517            prop_assume!(!a.is_nan() && a != 0.0);
518            let result = Calculator::divide(a, a).unwrap();
519            prop_assert!((result - 1.0).abs() < 1e-10);
520        }
521
522        #[test]
523        fn prop_power_zero_exponent(a in 1.0f64..1e5f64) {
524            let result = Calculator::power(a, 0.0);
525            prop_assert_eq!(result, Ok(1.0));
526        }
527
528        #[test]
529        fn prop_power_one_exponent(a in -1e5f64..1e5f64) {
530            prop_assume!(!a.is_nan());
531            let result = Calculator::power(a, 1.0);
532            prop_assert_eq!(result, Ok(a));
533        }
534    }
535}