1"""
2Calculator Module
3
4Simple calculator implementation to demonstrate testing.
5This module will be tested in test_calculator.py.
6"""
7
8from typing import Union, List
9import math
10
11
12class Calculator:
13 """
14 Basic calculator with arithmetic operations.
15 Demonstrates testable code design.
16 """
17
18 def add(self, a: float, b: float) -> float:
19 """Add two numbers"""
20 return a + b
21
22 def subtract(self, a: float, b: float) -> float:
23 """Subtract b from a"""
24 return a - b
25
26 def multiply(self, a: float, b: float) -> float:
27 """Multiply two numbers"""
28 return a * b
29
30 def divide(self, a: float, b: float) -> float:
31 """
32 Divide a by b.
33
34 Raises:
35 ValueError: If b is zero
36 """
37 if b == 0:
38 raise ValueError("Cannot divide by zero")
39 return a / b
40
41 def power(self, base: float, exponent: float) -> float:
42 """Raise base to the power of exponent"""
43 return base ** exponent
44
45 def square_root(self, x: float) -> float:
46 """
47 Calculate square root.
48
49 Raises:
50 ValueError: If x is negative
51 """
52 if x < 0:
53 raise ValueError("Cannot calculate square root of negative number")
54 return math.sqrt(x)
55
56 def modulo(self, a: float, b: float) -> float:
57 """
58 Calculate a modulo b.
59
60 Raises:
61 ValueError: If b is zero
62 """
63 if b == 0:
64 raise ValueError("Cannot calculate modulo with zero divisor")
65 return a % b
66
67
68class ScientificCalculator(Calculator):
69 """
70 Extended calculator with scientific functions.
71 Demonstrates inheritance testing.
72 """
73
74 def sin(self, x: float) -> float:
75 """Calculate sine (x in radians)"""
76 return math.sin(x)
77
78 def cos(self, x: float) -> float:
79 """Calculate cosine (x in radians)"""
80 return math.cos(x)
81
82 def tan(self, x: float) -> float:
83 """Calculate tangent (x in radians)"""
84 return math.tan(x)
85
86 def log(self, x: float, base: float = math.e) -> float:
87 """
88 Calculate logarithm.
89
90 Args:
91 x: Value to calculate log of
92 base: Logarithm base (default: e for natural log)
93
94 Raises:
95 ValueError: If x <= 0 or base <= 0 or base == 1
96 """
97 if x <= 0:
98 raise ValueError("Logarithm input must be positive")
99 if base <= 0 or base == 1:
100 raise ValueError("Logarithm base must be positive and not equal to 1")
101 return math.log(x, base)
102
103 def factorial(self, n: int) -> int:
104 """
105 Calculate factorial.
106
107 Raises:
108 ValueError: If n is negative
109 TypeError: If n is not an integer
110 """
111 if not isinstance(n, int):
112 raise TypeError("Factorial requires integer input")
113 if n < 0:
114 raise ValueError("Factorial not defined for negative numbers")
115 return math.factorial(n)
116
117
118class StatisticsCalculator:
119 """
120 Calculator for statistical operations.
121 Demonstrates testing with data structures.
122 """
123
124 def mean(self, numbers: List[float]) -> float:
125 """
126 Calculate arithmetic mean.
127
128 Raises:
129 ValueError: If list is empty
130 """
131 if not numbers:
132 raise ValueError("Cannot calculate mean of empty list")
133 return sum(numbers) / len(numbers)
134
135 def median(self, numbers: List[float]) -> float:
136 """
137 Calculate median.
138
139 Raises:
140 ValueError: If list is empty
141 """
142 if not numbers:
143 raise ValueError("Cannot calculate median of empty list")
144
145 sorted_numbers = sorted(numbers)
146 n = len(sorted_numbers)
147
148 if n % 2 == 0:
149 # Even number of elements - average of two middle values
150 return (sorted_numbers[n // 2 - 1] + sorted_numbers[n // 2]) / 2
151 else:
152 # Odd number of elements - middle value
153 return sorted_numbers[n // 2]
154
155 def mode(self, numbers: List[float]) -> float:
156 """
157 Calculate mode (most frequent value).
158
159 Raises:
160 ValueError: If list is empty or no unique mode
161 """
162 if not numbers:
163 raise ValueError("Cannot calculate mode of empty list")
164
165 frequency = {}
166 for num in numbers:
167 frequency[num] = frequency.get(num, 0) + 1
168
169 max_freq = max(frequency.values())
170 modes = [num for num, freq in frequency.items() if freq == max_freq]
171
172 if len(modes) > 1:
173 raise ValueError("No unique mode exists")
174
175 return modes[0]
176
177 def variance(self, numbers: List[float]) -> float:
178 """
179 Calculate variance.
180
181 Raises:
182 ValueError: If list is empty
183 """
184 if not numbers:
185 raise ValueError("Cannot calculate variance of empty list")
186
187 avg = self.mean(numbers)
188 return sum((x - avg) ** 2 for x in numbers) / len(numbers)
189
190 def standard_deviation(self, numbers: List[float]) -> float:
191 """
192 Calculate standard deviation.
193
194 Raises:
195 ValueError: If list is empty
196 """
197 return math.sqrt(self.variance(numbers))
198
199
200class CalculatorMemory:
201 """
202 Calculator with memory functionality.
203 Demonstrates stateful testing.
204 """
205
206 def __init__(self):
207 self._memory: float = 0.0
208 self._history: List[float] = []
209
210 def store(self, value: float) -> None:
211 """Store value in memory"""
212 self._memory = value
213 self._history.append(value)
214
215 def recall(self) -> float:
216 """Recall value from memory"""
217 return self._memory
218
219 def clear(self) -> None:
220 """Clear memory"""
221 self._memory = 0.0
222
223 def add_to_memory(self, value: float) -> None:
224 """Add value to current memory"""
225 self._memory += value
226 self._history.append(self._memory)
227
228 def get_history(self) -> List[float]:
229 """Get calculation history"""
230 return self._history.copy()
231
232 def clear_history(self) -> None:
233 """Clear calculation history"""
234 self._history.clear()
235
236
237# Helper functions for demonstrating pure function testing
238def is_even(n: int) -> bool:
239 """Check if number is even"""
240 return n % 2 == 0
241
242
243def is_prime(n: int) -> bool:
244 """
245 Check if number is prime.
246
247 Args:
248 n: Number to check
249
250 Returns:
251 True if prime, False otherwise
252 """
253 if n < 2:
254 return False
255 if n == 2:
256 return True
257 if n % 2 == 0:
258 return False
259
260 for i in range(3, int(math.sqrt(n)) + 1, 2):
261 if n % i == 0:
262 return False
263
264 return True
265
266
267def fibonacci(n: int) -> int:
268 """
269 Calculate nth Fibonacci number (0-indexed).
270
271 Raises:
272 ValueError: If n is negative
273 """
274 if n < 0:
275 raise ValueError("Fibonacci not defined for negative numbers")
276 if n <= 1:
277 return n
278
279 a, b = 0, 1
280 for _ in range(n - 1):
281 a, b = b, a + b
282 return b
283
284
285def gcd(a: int, b: int) -> int:
286 """
287 Calculate greatest common divisor using Euclidean algorithm.
288
289 Raises:
290 ValueError: If either number is negative
291 """
292 if a < 0 or b < 0:
293 raise ValueError("GCD not defined for negative numbers")
294
295 while b:
296 a, b = b, a % b
297 return a
298
299
300if __name__ == "__main__":
301 # Simple demonstration
302 calc = Calculator()
303 print("Calculator Demo:")
304 print(f"5 + 3 = {calc.add(5, 3)}")
305 print(f"10 - 4 = {calc.subtract(10, 4)}")
306 print(f"6 * 7 = {calc.multiply(6, 7)}")
307 print(f"15 / 3 = {calc.divide(15, 3)}")
308 print(f"2^8 = {calc.power(2, 8)}")
309 print(f"√16 = {calc.square_root(16)}")
310
311 sci_calc = ScientificCalculator()
312 print(f"\nScientific Calculator Demo:")
313 print(f"sin(π/2) = {sci_calc.sin(math.pi / 2):.4f}")
314 print(f"cos(0) = {sci_calc.cos(0):.4f}")
315 print(f"5! = {sci_calc.factorial(5)}")
316
317 stats_calc = StatisticsCalculator()
318 data = [1, 2, 3, 4, 5]
319 print(f"\nStatistics Demo:")
320 print(f"Mean of {data} = {stats_calc.mean(data)}")
321 print(f"Median of {data} = {stats_calc.median(data)}")
322 print(f"Std Dev of {data} = {stats_calc.standard_deviation(data):.4f}")
323
324 print(f"\nHelper Functions Demo:")
325 print(f"is_prime(17) = {is_prime(17)}")
326 print(f"fibonacci(10) = {fibonacci(10)}")
327 print(f"gcd(48, 18) = {gcd(48, 18)}")