diff --git a/mathstream/README.md b/mathstream/README.md index 46471bc..e3abb68 100644 --- a/mathstream/README.md +++ b/mathstream/README.md @@ -13,7 +13,17 @@ pip install -e . Create digit files anywhere you like (the examples below use `instance/log`), then construct `StreamNumber` objects, and call the helpers: ```python -from mathstream import StreamNumber, add, sub, mul, div, pow +from mathstream import ( + StreamNumber, + add, + sub, + mul, + div, + mod, + pow, + is_even, + is_odd, +) a = StreamNumber("instance/log/huge.txt") b = StreamNumber("instance/log/tiny.txt") @@ -23,7 +33,10 @@ print("sum =", "".join(add(a, b).stream())) print("difference =", "".join(sub(a, b).stream())) print("product =", "".join(mul(a, b).stream())) print("quotient =", "".join(div(a, b).stream())) +print("modulo =", "".join(mod(a, b).stream())) print("power =", "".join(pow(a, e).stream())) +print("a is even?", is_even(a)) +print("b is odd?", is_odd(b)) ``` Each arithmetic call writes its result back into `instance/log` (configurable via `mathstream.number.LOG_DIR`) so you can stream the digits later or reuse them in further operations. @@ -33,15 +46,16 @@ Each arithmetic call writes its result back into `instance/log` (configurable vi - **StreamNumber(path)** – Wraps a digit text file. The file may include an optional leading sign (`+` or `-`). Whitespace is ignored; digits must otherwise be contiguous. - **`.stream(chunk_size)`** – Yields strings of digits with the provided chunk size. Operations in `mathstream.engine` consume these streams to avoid loading the entire number at once. - **Automatic staging** – Outputs are stored under `LOG_DIR` with hashes based on input file paths, letting you compose operations without manual bookkeeping. -- **Sign-aware** – Addition, subtraction, multiplication, division (`//` behavior), and exponentiation (non-negative exponents) all respect operand sign. Division follows Python’s floor division rules. +- **Sign-aware** – Addition, subtraction, multiplication, division (`//` behavior), modulo, and exponentiation (non-negative exponents) all respect operand sign. Division/modulo follow Python’s floor-division rules. - **Utilities** – `clear_logs()` wipes prior staged results so you can start fresh. +- **Parity helpers** – `is_even` and `is_odd` inspect the streamed digits without materializing the integer. ## Example Script `test.py` in the repository root demonstrates a minimal workflow: 1. Writes sample operands to `tests/*.txt`. -2. Calls every arithmetic primitive. +2. Calls every arithmetic primitive plus the modulo/parity helpers. 3. Asserts that the streamed outputs match known values (helpful for quick regression checks). Run it via: diff --git a/mathstream/__init__.py b/mathstream/__init__.py index d1b5a84..3285c4a 100644 --- a/mathstream/__init__.py +++ b/mathstream/__init__.py @@ -1,2 +1,11 @@ -from .engine import clear_logs, add, sub, mul, div, pow +from .engine import clear_logs, add, sub, mul, div, mod, pow, is_even, is_odd from .number import StreamNumber + +__all__ = [ + "clear_logs", + "add", "sub", + "mul", "div", "mod", + "pow", + "is_even", "is_odd", + "StreamNumber", +] diff --git a/mathstream/engine.py b/mathstream/engine.py index 85a0c21..3691df8 100644 --- a/mathstream/engine.py +++ b/mathstream/engine.py @@ -261,6 +261,26 @@ def div(num_a: StreamNumber, num_b: StreamNumber) -> StreamNumber: return _write_result("div", (num_a, num_b), result) +def mod(num_a: StreamNumber, num_b: StreamNumber) -> StreamNumber: + """Return num_a % num_b following Python's floor-division semantics.""" + sign_a, a_digits = _normalize_stream(num_a) + sign_b, b_digits = _normalize_stream(num_b) + + _, remainder = _divide_abs(a_digits, b_digits) + + if remainder == "0": + return _write_result("mod", (num_a, num_b), "0") + + if sign_a == sign_b: + digits = remainder + else: + digits = _sub_abs(b_digits, remainder) + + sign = 1 if sign_b > 0 else -1 + result = digits if sign > 0 else f"-{digits}" + return _write_result("mod", (num_a, num_b), result) + + def pow(num_a: StreamNumber, num_b: StreamNumber) -> StreamNumber: """Return num_a ** num_b using repeated squaring (integer exponent only).""" base_sign, base_digits = _normalize_stream(num_a) @@ -289,3 +309,14 @@ def pow(num_a: StreamNumber, num_b: StreamNumber) -> StreamNumber: result_sign = 1 result = result_digits if result_sign > 0 else f"-{result_digits}" return _write_result("pow", (num_a, num_b), result) + + +def is_even(num: StreamNumber) -> bool: + """Return True if the streamed integer is even.""" + _, digits = _normalize_stream(num) + return (ord(digits[-1]) - 48) % 2 == 0 + + +def is_odd(num: StreamNumber) -> bool: + """Return True if the streamed integer is odd.""" + return not is_even(num) diff --git a/test.py b/test.py index 433267a..69d8e60 100644 --- a/test.py +++ b/test.py @@ -2,7 +2,18 @@ from __future__ import annotations from pathlib import Path -from mathstream import StreamNumber, add, sub, mul, div, pow, clear_logs +from mathstream import ( + StreamNumber, + add, + sub, + mul, + div, + mod, + pow, + is_even, + is_odd, + clear_logs, +) NUMBERS_DIR = Path(__file__).parent / "tests" @@ -27,6 +38,11 @@ def check(label: str, result: StreamNumber, expected: str) -> None: print(f"{label} = {actual}") +def check_bool(label: str, value: bool, expected: bool) -> None: + assert value is expected, f"{label} expected {expected}, got {value}" + print(f"{label} = {value}") + + def main() -> None: clear_logs() @@ -35,6 +51,7 @@ def main() -> None: small = write_number("tiny", "34567") negative = write_number("negative", "-1200") exponent = write_number("power", "5") + negative_divisor = write_number("neg_divisor", "-34567") # Showcase the core operations. total = add(big, small) @@ -42,6 +59,10 @@ def main() -> None: product = mul(small, negative) quotient = div(big, small) powered = pow(small, exponent) + modulus = mod(big, small) + neg_mod_pos = mod(negative, small) + pos_mod_neg = mod(small, negative) + neg_mod_neg = mod(negative, negative_divisor) print("Operands stored under:", NUMBERS_DIR) check("huge + tiny", total, "98765432123491356") @@ -49,6 +70,14 @@ def main() -> None: check("tiny * negative", product, "-41480400") check("huge // tiny", quotient, "2857217349595") check("tiny ** power", powered, "49352419431622775997607") + check("huge % tiny", modulus, "6424") + check("negative % tiny", neg_mod_pos, "33367") + check("tiny % negative", pos_mod_neg, "-233") + check("negative % neg_divisor", neg_mod_neg, "-1200") + check_bool("is_even(negative)", is_even(negative), True) + check_bool("is_even(tiny)", is_even(small), False) + check_bool("is_odd(tiny)", is_odd(small), True) + check_bool("is_odd(negative)", is_odd(negative), False) if __name__ == "__main__": diff --git a/tests/neg_divisor.txt b/tests/neg_divisor.txt new file mode 100644 index 0000000..2be6f0d --- /dev/null +++ b/tests/neg_divisor.txt @@ -0,0 +1 @@ +-34567 \ No newline at end of file