diff --git a/exceptions.py b/exceptions.py new file mode 100644 index 0000000..3a45308 --- /dev/null +++ b/exceptions.py @@ -0,0 +1,48 @@ +class calculatorerror(Exception): + def __init__(self, msg="calculator error"): + self.msg = msg + super().__init__(self.msg) + +class invalidinputerror(calculatorerror): + def __init__(self, msg="invalid input"): + super().__init__(msg) + +class zerodenominatorerror(calculatorerror): + def __init__(self, msg="denominator cannot be zero"): + super().__init__(msg) + +class invalidfractionerror(calculatorerror): + def __init__(self, msg="invalid fraction format"): + super().__init__(msg) + +class invalidcomplexerror(calculatorerror): + def __init__(self, msg="invalid complex number"): + super().__init__(msg) + +class invalidbinaryerror(calculatorerror): + def __init__(self, msg="invalid binary number"): + super().__init__(msg) + +class invalidoctalerror(calculatorerror): + def __init__(self, msg="invalid octal number"): + super().__init__(msg) + +class invalidhexerror(calculatorerror): + def __init__(self, msg="invalid hex number"): + super().__init__(msg) + +class invalidseterror(calculatorerror): + def __init__(self, msg="invalid set format"): + super().__init__(msg) + +class invalidmatrixerror(calculatorerror): + def __init__(self, msg="invalid matrix format"): + super().__init__(msg) + +class dimensionmismatcherror(calculatorerror): + def __init__(self, msg="matrix dimensions dont match"): + super().__init__(msg) + +class undefinedoperationerror(calculatorerror): + def __init__(self, msg="operation is undefined"): + super().__init__(msg) diff --git a/fraction.py b/fraction.py new file mode 100644 index 0000000..f15c0ec --- /dev/null +++ b/fraction.py @@ -0,0 +1,74 @@ +import math +from exceptions import zerodenominatorerror, invalidfractionerror + + +def parse_frac(s): + s = s.strip() + if '/' in s: + parts = s.split('/') + if len(parts) != 2: + raise invalidfractionerror(f"bad fraction: '{s}'") + try: + num = int(parts[0].strip()) + den = int(parts[1].strip()) + except ValueError: + raise invalidfractionerror(f"bad fraction: '{s}'") + else: + try: + num = int(s) + den = 1 + except ValueError: + raise invalidfractionerror(f"bad fraction: '{s}'") + if den == 0: + raise zerodenominatorerror("denominator is zero") + return num, den + + +def simplify(num, den): + if num == 0: + return 0, 1 + if den < 0: + num, den = -num, -den + g = math.gcd(abs(num), abs(den)) + return num // g, den // g + + +def format_frac(num, den): + num, den = simplify(num, den) + if den == 1: + return str(num) + return f"{num}/{den}" + + +def add_fraction(a, b): + n1, d1 = parse_frac(a) + n2, d2 = parse_frac(b) + num = n1 * d2 + n2 * d1 + den = d1 * d2 + return format_frac(num, den) + + +def sub_fraction(a, b): + n1, d1 = parse_frac(a) + n2, d2 = parse_frac(b) + num = n1 * d2 - n2 * d1 + den = d1 * d2 + return format_frac(num, den) + + +def mul_fraction(a, b): + n1, d1 = parse_frac(a) + n2, d2 = parse_frac(b) + num = n1 * n2 + den = d1 * d2 + return format_frac(num, den) + + +def div_fraction(a, b): + n1, d1 = parse_frac(a) + n2, d2 = parse_frac(b) + if n2 == 0: + raise zerodenominatorerror("cant divide by zero fraction") + num = n1 * d2 + den = d1 * n2 + return format_frac(num, den) diff --git a/hex.py b/hex.py new file mode 100644 index 0000000..76cc935 --- /dev/null +++ b/hex.py @@ -0,0 +1,68 @@ +from exceptions import invalidhexerror + + +def check_hex(h): + h = h.strip() + if not h: + raise invalidhexerror("empty hex string") + valid = set('0123456789ABCDEFabcdef') + for ch in h: + if ch not in valid: + raise invalidhexerror(f"invalid character: '{ch}'") + + +def hex_to_decimal(h): + h = h.strip() + check_hex(h) + return str(int(h, 16)) + + +def decimal_to_hex(d): + num = int(d.strip()) + if num < 0: + return '-' + hex(abs(num))[2:].upper() + return hex(num)[2:].upper() + + +def hex_add(a, b): + a, b = a.strip(), b.strip() + check_hex(a) + check_hex(b) + res = int(a, 16) + int(b, 16) + return hex(res)[2:].upper() + + +def hex_subtract(a, b): + a, b = a.strip(), b.strip() + check_hex(a) + check_hex(b) + res = int(a, 16) - int(b, 16) + if res < 0: + return '-' + hex(abs(res))[2:].upper() + return hex(res)[2:].upper() + + +def hex_multiply(a, b): + a, b = a.strip(), b.strip() + check_hex(a) + check_hex(b) + res = int(a, 16) * int(b, 16) + return hex(res)[2:].upper() + + +def fifteens_complement(h): + h = h.strip() + check_hex(h) + result = [] + for ch in h.upper(): + val = int(ch, 16) + result.append(hex(15 - val)[2:].upper()) + return ''.join(result) + + +def sixteens_complement(h): + h = h.strip() + check_hex(h) + fifteens = fifteens_complement(h) + res = int(fifteens, 16) + 1 + return hex(res)[2:].upper() diff --git a/test_fraction.py b/test_fraction.py new file mode 100644 index 0000000..b7e9380 --- /dev/null +++ b/test_fraction.py @@ -0,0 +1,68 @@ +import unittest +from fraction import add_fraction, sub_fraction, mul_fraction, div_fraction +from exceptions import zerodenominatorerror, invalidfractionerror + + +class testfraction(unittest.TestCase): + + def test_fraction_add(self): + self.assertEqual(add_fraction('1/2', '1/4'), '3/4') + + def test_fraction_add_same_denom(self): + self.assertEqual(add_fraction('1/5', '2/5'), '3/5') + + def test_fraction_add_simplify(self): + self.assertEqual(add_fraction('1/4', '1/4'), '1/2') + + def test_fraction_add_whole_result(self): + self.assertEqual(add_fraction('1/2', '1/2'), '1') + + def test_fraction_add_negative(self): + self.assertEqual(add_fraction('-1/2', '1/4'), '-1/4') + + def test_fraction_add_zero(self): + self.assertEqual(add_fraction('0/5', '3/7'), '3/7') + + def test_fraction_sub(self): + self.assertEqual(sub_fraction('3/4', '1/4'), '1/2') + + def test_fraction_sub_negative_result(self): + self.assertEqual(sub_fraction('1/4', '3/4'), '-1/2') + + def test_fraction_sub_same(self): + self.assertEqual(sub_fraction('3/5', '3/5'), '0') + + def test_fraction_mul(self): + self.assertEqual(mul_fraction('2/3', '3/4'), '1/2') + + def test_fraction_mul_by_zero(self): + self.assertEqual(mul_fraction('2/3', '0/5'), '0') + + def test_fraction_mul_whole_number(self): + self.assertEqual(mul_fraction('1/2', '2'), '1') + + def test_fraction_div(self): + self.assertEqual(div_fraction('1/2', '1/4'), '2') + + def test_fraction_div_result_fraction(self): + self.assertEqual(div_fraction('1/3', '2/3'), '1/2') + + def test_fraction_div_by_zero(self): + with self.assertRaises(zerodenominatorerror): + div_fraction('1/2', '0/3') + + def test_fraction_zero_denominator(self): + with self.assertRaises(zerodenominatorerror): + add_fraction('1/0', '1/2') + + def test_fraction_invalid_format(self): + with self.assertRaises(invalidfractionerror): + add_fraction('abc', '1/2') + + def test_fraction_invalid_format_multiple_slashes(self): + with self.assertRaises(invalidfractionerror): + add_fraction('1/2/3', '1/2') + + +if __name__ == "__main__": + unittest.main() diff --git a/test_hex.py b/test_hex.py new file mode 100644 index 0000000..3fc6563 --- /dev/null +++ b/test_hex.py @@ -0,0 +1,67 @@ +import unittest +from hex import ( + hex_to_decimal, decimal_to_hex, hex_add, + hex_subtract, hex_multiply, fifteens_complement, sixteens_complement +) +from exceptions import invalidhexerror + + +class testhex(unittest.TestCase): + + def test_hex_to_decimal(self): + self.assertEqual(hex_to_decimal('1A5'), '421') + + def test_hex_to_decimal_zero(self): + self.assertEqual(hex_to_decimal('0'), '0') + + def test_hex_to_decimal_single(self): + self.assertEqual(hex_to_decimal('F'), '15') + + def test_hex_to_decimal_lowercase(self): + self.assertEqual(hex_to_decimal('ff'), '255') + + def test_decimal_to_hex(self): + self.assertEqual(decimal_to_hex('243'), 'F3') + + def test_decimal_to_hex_zero(self): + self.assertEqual(decimal_to_hex('0'), '0') + + def test_decimal_to_hex_255(self): + self.assertEqual(decimal_to_hex('255'), 'FF') + + def test_hex_add(self): + self.assertEqual(hex_add('1A', '0F'), '29') + + def test_hex_add_with_carry(self): + self.assertEqual(hex_add('FF', '1'), '100') + + def test_hex_subtract(self): + self.assertEqual(hex_subtract('1A', '0F'), 'B') + + def test_hex_multiply(self): + self.assertEqual(hex_multiply('A', '2'), '14') + + def test_fifteens_complement(self): + self.assertEqual(fifteens_complement('1A5'), 'E5A') + + def test_fifteens_complement_all_f(self): + self.assertEqual(fifteens_complement('FFF'), '000') + + def test_sixteens_complement(self): + self.assertEqual(sixteens_complement('1A5'), 'E5B') + + def test_invalid_hex_char(self): + with self.assertRaises(invalidhexerror): + hex_to_decimal('1G5') + + def test_invalid_hex_special(self): + with self.assertRaises(invalidhexerror): + hex_to_decimal('12#') + + def test_invalid_hex_empty(self): + with self.assertRaises(invalidhexerror): + hex_to_decimal('') + + +if __name__ == "__main__": + unittest.main()