1use crate::core::{AnomalyValidator, CalcError, CalcResult};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Operation {
10 Add,
12 Subtract,
14 Multiply,
16 Divide,
18 Modulo,
20 Power,
22}
23
24impl Operation {
25 #[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 #[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 #[must_use]
50 pub const fn is_left_associative(&self) -> bool {
51 !matches!(self, Self::Power)
52 }
53}
54
55#[derive(Debug, Default)]
57pub struct Calculator {
58 pub(crate) validator: AnomalyValidator,
60}
61
62impl Calculator {
63 #[must_use]
65 pub fn new() -> Self {
66 Self {
67 validator: AnomalyValidator::new(),
68 }
69 }
70
71 #[must_use]
73 pub fn with_validator(validator: AnomalyValidator) -> Self {
74 Self { validator }
75 }
76
77 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 self.validator
90 .validate(raw_result)
91 .map_err(|v| CalcError::AnomalyViolation(v))
92 }
93
94 pub fn add(a: f64, b: f64) -> CalcResult<f64> {
96 let result = a + b;
97 Self::check_overflow(result)
98 }
99
100 pub fn subtract(a: f64, b: f64) -> CalcResult<f64> {
102 let result = a - b;
103 Self::check_overflow(result)
104 }
105
106 pub fn multiply(a: f64, b: f64) -> CalcResult<f64> {
108 let result = a * b;
109 Self::check_overflow(result)
110 }
111
112 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 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 pub fn power(a: f64, b: f64) -> CalcResult<f64> {
132 let result = a.powf(b);
133 Self::check_overflow(result)
134 }
135
136 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 let result = Calculator::power(-2.0, 0.5);
415 assert!(matches!(result, Err(CalcError::InvalidResult(_))));
416 }
417
418 #[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 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}