from mcp_calculator.ops.context import MathError from mcp_calculator.ops.number_theory import number_theory from mcp_calculator.ops.units import convert_units from tests.support import MathTestCase class NumberTheoryTest(MathTestCase): def test_operations(self): cases = [ ("gcd", [48, 180], 12), ("lcm", [4, 6, 10], 60), ("is_prime", [97], True), ("is_prime", ["2305843009213693951"], True), ("next_prime", [100], 101), ("previous_prime", [100], 97), ("nth_prime", [10], 29), ("totient", [36], 12), ("mod", [-7, 3], 2), ("mod_pow", [2, 100, 97], 16), ("mod_inverse", [3, 11], 4), ("binomial", [49, 6], 13983816), ("permutations", [5, 2], 20), ("fibonacci", [50], 12586269025), ("factorial", [10], 3628800), ] for operation, values, expected in cases: with self.subTest(operation=operation, values=values): self.assertEqual(number_theory(operation, values)["result"], expected) def test_big_results_are_strings(self): r = number_theory("factorial", [30]) self.assertEqual(r["result"], "265252859812191058636308480000000") self.assertEqual(r["digits"], 33) def test_factorize(self): r = number_theory("factorize", [360]) self.assertEqual(r["result"], {"2": 3, "3": 2, "5": 1}) self.assertEqual(r["product_form"], "2^3 * 3^2 * 5") def test_crt(self): self.assertEqual(number_theory("crt", [2, 3, 2], moduli=[3, 5, 7])["result"], 23) def test_base_convert(self): self.assertEqual(number_theory("base_convert", ["ff"], from_base=16, to_base=2)["result"], "11111111") self.assertEqual(number_theory("base_convert", ["0x1F"], from_base=16, to_base=10)["result"], "31") self.assertEqual(number_theory("base_convert", ["-10"], to_base=2)["result"], "-1010") def test_continued_fraction(self): self.assertEqual(number_theory("continued_fraction", ["415/93"])["result"], [4, 2, 6, 7]) def test_primes_in_range(self): self.assertEqual(number_theory("primes_in_range", [10, 30])["result"], [11, 13, 17, 19, 23, 29]) def test_arity_errors(self): with self.assertRaisesRegex(MathError, "values"): number_theory("mod_pow", [2, 3]) def test_no_inverse(self): with self.assertRaisesRegex(MathError, "no inverse"): number_theory("mod_inverse", [2, 4]) def test_non_integer(self): with self.assertRaisesRegex(MathError, "not an integer"): number_theory("gcd", ["1.5", 3]) class UnitsTest(MathTestCase): def test_speed(self): self.assertApprox(convert_units("100 km/h", "m/s")["magnitude"], 27.7777777778) def test_temperature(self): self.assertApprox(convert_units("25 degC", "degF")["magnitude"], 77) def test_quantity_arithmetic(self): self.assertApprox(convert_units("5 km/h * 2 h", "m")["magnitude"], 10000) def test_force(self): self.assertApprox(convert_units("1.5 kg * 9.81 m/s^2", "N")["magnitude"], 14.715) def test_base_units(self): r = convert_units("3 kWh") self.assertApprox(r["magnitude"], 10_800_000) self.assertIn("note", r) def test_data_units(self): self.assertApprox(convert_units("1 GiB", "MB")["magnitude"], 1073.741824) def test_incompatible(self): with self.assertRaisesRegex(MathError, "incompatible"): convert_units("5 kg", "m") def test_unknown_unit(self): with self.assertRaisesRegex(MathError, "unknown"): convert_units("5 flurbs", "m")