| """Big number calculator — arbitrary precision integer/decimal arithmetic. |
| |
| All operations use Python's built-in big integers scaled by 10^n for decimal tracking. |
| No floating point at any intermediate step for +, -, *, /, pow (integer exponent). |
| |
| Operations: add, sub, mul, div, pow, sqrt, square, fact, mod, gcd, lcm |
| """ |
|
|
| import math |
| import re |
|
|
|
|
| def parse_number(s: str): |
| """Parse a number string into (sign: int, int_part: str, frac_part: str). |
| |
| Supports: |
| - Integers: 42, -123 |
| - Decimals: 3.14, -0.001 |
| - Scientific notation: 2.5e3, 1.23E-4, -5e10 |
| |
| Returns None on invalid input. |
| """ |
| s = (s or '').strip() |
| if not s: |
| return None |
|
|
| sign = 1 |
| if s[0] == '-': |
| sign = -1 |
| s = s[1:] |
| elif s[0] == '+': |
| s = s[1:] |
| if not s or s == '.': |
| return None |
|
|
| |
| exp = 0 |
| m = re.search(r'[eE]', s) |
| if m: |
| e_str = s[m.end():] |
| if not e_str or (e_str[0] not in '+-' and not e_str[0].isdigit()): |
| return None |
| try: |
| exp = int(e_str) |
| except ValueError: |
| return None |
| s = s[:m.start()] |
| if not s or s == '.': |
| return None |
|
|
| |
| if '.' in s: |
| int_part, frac_part = s.split('.', 1) |
| int_part = int_part.lstrip('0') or '0' |
| frac_part = frac_part.rstrip('0') |
| else: |
| int_part = s.lstrip('0') or '0' |
| frac_part = '' |
|
|
| if not int_part.isdigit() or (frac_part and not frac_part.isdigit()): |
| return None |
|
|
| |
| if exp > 0: |
| move = min(exp, len(frac_part)) |
| int_part += frac_part[:move] |
| frac_part = frac_part[move:] |
| exp -= move |
| if exp > 0: |
| int_part += '0' * exp |
| exp = 0 |
| elif exp < 0: |
| shift = -exp |
| |
| take = min(shift, len(int_part)) |
| frac_part = int_part[-take:] + frac_part |
| int_part = int_part[:-take] or '0' |
| shift -= take |
| |
| if shift > 0: |
| frac_part = '0' * shift + frac_part |
|
|
| int_part = int_part.lstrip('0') or '0' |
| frac_part = frac_part.rstrip('0') |
| return (sign, int_part, frac_part) |
|
|
|
|
| def to_bigint(n, target_dec): |
| """Convert parsed number to Python int scaled by 10^target_dec.""" |
| sign, int_part, frac_part = n |
| s = int_part + (frac_part or '').ljust(target_dec, '0') |
| s = s.lstrip('0') or '0' |
| val = int(s) |
| return -val if sign == -1 else val |
|
|
|
|
| def from_bigint(val, dec_places, precision): |
| """Format a bigint with decimal tracking back to string. |
| |
| Rounds to `precision` digits after the decimal point. |
| """ |
| if val == 0: |
| return '0' |
| negative = val < 0 |
| s = str(-val) if negative else str(val) |
|
|
| if dec_places <= 0: |
| if dec_places < 0: |
| s += '0' * (-dec_places) |
| return ('-' if negative else '') + s |
|
|
| |
| while len(s) <= dec_places: |
| s = '0' + s |
| ins = len(s) - dec_places |
| int_part = s[:ins] or '0' |
| frac_part = s[ins:] |
|
|
| |
| if len(frac_part) > precision: |
| round_digit = int(frac_part[precision]) |
| frac_part = frac_part[:precision] |
| if round_digit >= 5: |
| new_s = str(int(int_part + frac_part) + 1) |
| if len(new_s) > len(int_part) + len(frac_part): |
| int_part = new_s[:len(new_s) - len(frac_part)] |
| frac_part = new_s[len(new_s) - len(frac_part):] |
| else: |
| int_part = new_s[:len(new_s) - len(frac_part)] or '0' |
| frac_part = new_s[len(new_s) - len(frac_part):] |
|
|
| frac_part = frac_part.rstrip('0') |
| result = int_part |
| if frac_part: |
| result += '.' + frac_part |
| return ('-' if negative else '') + result |
|
|
|
|
| |
|
|
| def _str_cmp(a, b): |
| """Compare two positive digit strings.""" |
| if len(a) != len(b): |
| return 1 if len(a) > len(b) else -1 |
| if a == b: |
| return 0 |
| return 1 if a > b else -1 |
|
|
|
|
| def _str_add(a, b): |
| """Add two positive integer strings.""" |
| carry = 0 |
| result = [] |
| i, j = len(a) - 1, len(b) - 1 |
| while i >= 0 or j >= 0 or carry: |
| da = int(a[i]) if i >= 0 else 0 |
| db = int(b[j]) if j >= 0 else 0 |
| s = da + db + carry |
| result.append(str(s % 10)) |
| carry = s // 10 |
| i -= 1; j -= 1 |
| return ''.join(reversed(result)) |
|
|
|
|
| def _str_sub(a, b): |
| """Subtract b from a (|a| >= |b|), both positive digit strings.""" |
| borrow = 0 |
| result = [] |
| i, j = len(a) - 1, len(b) - 1 |
| while i >= 0: |
| da = int(a[i]) |
| db = int(b[j]) if j >= 0 else 0 |
| d = da - db - borrow |
| if d < 0: |
| d += 10 |
| borrow = 1 |
| else: |
| borrow = 0 |
| result.append(str(d)) |
| i -= 1; j -= 1 |
| return ''.join(reversed(result)).lstrip('0') or '0' |
|
|
|
|
| def _str_mul(a, b): |
| """Multiply two positive integer strings.""" |
| if a == '0' or b == '0': |
| return '0' |
| res = [0] * (len(a) + len(b)) |
| for i in range(len(a) - 1, -1, -1): |
| for j in range(len(b) - 1, -1, -1): |
| p = int(a[i]) * int(b[j]) + res[i + j + 1] |
| res[i + j + 1] = p % 10 |
| res[i + j] += p // 10 |
| return ''.join(map(str, res)).lstrip('0') or '0' |
|
|
|
|
| def _str_divmod(dividend, divisor): |
| """Divide two positive integer strings. Returns (quotient, remainder).""" |
| if divisor == '0': |
| return None |
| quot = [] |
| rem = '' |
| for ch in dividend: |
| rem += ch |
| rem = rem.lstrip('0') or '0' |
| count = 0 |
| while _str_cmp(rem, divisor) >= 0: |
| rem = _str_sub(rem, divisor) |
| count += 1 |
| quot.append(str(count)) |
| q = ''.join(quot).lstrip('0') or '0' |
| return (q, rem) |
|
|
|
|
| def _gcd_str(a, b): |
| """GCD of two positive integer strings.""" |
| while b != '0': |
| _, rem = _str_divmod(a, b) |
| a, b = b, rem |
| if a is None: |
| return None |
| return a |
|
|
|
|
| def _bigint_sqrt(n): |
| """Integer square root using Newton's method (floor).""" |
| if n < 0: |
| return None |
| if n == 0: |
| return 0 |
| x = n |
| y = (x + 1) // 2 |
| while y < x: |
| x = y |
| y = (y + n // y) // 2 |
| return x |
|
|
|
|
| |
|
|
| def add(x_str, y_str, precision=20): |
| """X + Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| dec = max(len(x[2]), len(y[2])) |
| a = to_bigint(x, dec) |
| b = to_bigint(y, dec) |
| return from_bigint(a + b, dec, precision) |
|
|
|
|
| def sub(x_str, y_str, precision=20): |
| """X - Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| dec = max(len(x[2]), len(y[2])) |
| a = to_bigint(x, dec) |
| b = to_bigint(y, dec) |
| return from_bigint(a - b, dec, precision) |
|
|
|
|
| def mul(x_str, y_str, precision=20): |
| """X × Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| dec_x = len(x[2]) |
| dec_y = len(y[2]) |
| a = to_bigint(x, 0) |
| b = to_bigint(y, 0) |
| return from_bigint(a * b, dec_x + dec_y, precision) |
|
|
|
|
| def div(x_str, y_str, precision=20): |
| """X / Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| if y[1] == '0' and not y[2]: |
| return None |
| dec_x = len(x[2]) |
| dec_y = len(y[2]) |
| scale = dec_y + precision |
| a = to_bigint(x, scale) |
| b = to_bigint(y, 0) |
| if b == 0: |
| return None |
| result = a // b |
| final_dec = scale - dec_x |
| sign_mul = 1 if x[0] == y[0] else -1 |
| return from_bigint(result * sign_mul, final_dec, precision) |
|
|
|
|
| def pow_int(x_str, y_str, precision=20): |
| """X^Y (Y as integer). Returns None if result too large.""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| if y[0] == -1: |
| return '0' |
| if y[2]: |
| |
| return pow_float(x_str, y_str, precision) |
|
|
| |
| exp_str = y[1] |
| if exp_str == '0': |
| return '1' |
|
|
| if len(exp_str) > 10: |
| return None |
|
|
| x_sign = x[0] |
| x_dec = len(x[2]) |
| x_str_clean = x[1] + (x[2] or '') |
|
|
| result = '1' |
| base = x_str_clean |
| e = exp_str |
|
|
| while e != '0': |
| if int(e[-1]) % 2 == 1: |
| |
| res_clean = result.lstrip('0') or '0' |
| result = _str_mul(res_clean, base) |
| |
| res_scale = 0 |
| base_scale = len(e) |
| |
| base = _str_mul(base, base) |
| e = _str_divmod(e, '2')[0] |
|
|
| return result |
|
|
|
|
| def pow_float(x_str, y_str, precision=20): |
| """X^Y via float approximation (used when Y is decimal).""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| x_val = float(x[0] * int(x[1] + (x[2] or '0')[:15])) / (10 ** len(x[2])) |
| y_val = float(y[0] * int(y[1] + (y[2] or '0')[:15])) / (10 ** len(y[2])) |
| if x_val <= 0: |
| return None |
| result = x_val ** y_val |
| if not math.isfinite(result): |
| return None |
| return format(float_result_to_str(result, precision)) |
|
|
|
|
| def sqrt(x_str, precision=20): |
| """√X""" |
| x = parse_number(x_str) |
| if x is None: |
| return None |
| if x[0] == -1: |
| return None |
| x_dec = len(x[2]) |
| scale = precision * 2 |
| a = to_bigint(x, scale) |
| r = _bigint_sqrt(a) |
| if r is None: |
| return None |
| return from_bigint(r, precision, precision) |
|
|
|
|
| def square(x_str, precision=20): |
| """X²""" |
| return mul(x_str, x_str, precision) |
|
|
|
|
| def fact(x_str, precision=0): |
| """X! (integer factorial)""" |
| x = parse_number(x_str) |
| if x is None: |
| return None |
| if x[0] == -1: |
| return None |
| if x[2]: |
| return None |
| n = x[1] |
| if n in ('0', '1'): |
| return '1' |
|
|
| n_int = int(n) |
| if n_int > 100000: |
| return None |
|
|
| result = '1' |
| for i in range(2, n_int + 1): |
| result = _str_mul(result, str(i)) |
| return result |
|
|
|
|
| def mod(x_str, y_str, precision=0): |
| """X MOD Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| if y[1] == '0' and not y[2]: |
| return None |
| a = int(x[0] * int(x[1])) |
| b = int(y[0] * int(y[1])) |
| if b == 0: |
| return None |
| return str(a % b) |
|
|
|
|
| def gcd(x_str, y_str, precision=0): |
| """GCD of X and Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| a = x[1].lstrip('0') or '0' |
| b = y[1].lstrip('0') or '0' |
| if a == '0' and b == '0': |
| return '0' |
| if a == '0': |
| return b |
| if b == '0': |
| return a |
| return _gcd_str(a, b) |
|
|
|
|
| def lcm(x_str, y_str, precision=0): |
| """LCM of X and Y""" |
| x = parse_number(x_str) |
| y = parse_number(y_str) |
| if x is None or y is None: |
| return None |
| a = x[1].lstrip('0') or '0' |
| b = y[1].lstrip('0') or '0' |
| if a == '0' or b == '0': |
| return '0' |
| g = _gcd_str(a, b) |
| if g is None: |
| return None |
| prod = _str_mul(a, b) |
| q, _ = _str_divmod(prod, g) |
| return q |
|
|