ARBS / testing /bigcalc.py
CLIWorks's picture
Upload folder using huggingface_hub
d8bc908 verified
"""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
# Scientific notation
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
# Split integer and fractional parts
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
# Apply exponent (shift decimal point)
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 # number of places to shift left
# Move as many digits from int_part as possible
take = min(shift, len(int_part))
frac_part = int_part[-take:] + frac_part
int_part = int_part[:-take] or '0'
shift -= take
# Remaining shift: prepend zeros to frac_part
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
# Pad with leading zeros
while len(s) <= dec_places:
s = '0' + s
ins = len(s) - dec_places
int_part = s[:ins] or '0'
frac_part = s[ins:]
# Round
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
# ========== String-based integer helpers for very large numbers ==========
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
# ========== Core operations ==========
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 # division by zero
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' # negative exponent → 0 for integer precision
if y[2]:
# Decimal exponent — fall back to float approximation
return pow_float(x_str, y_str, precision)
# Integer exponent via binary exponentiation on strings
exp_str = y[1]
if exp_str == '0':
return '1'
if len(exp_str) > 10:
return None # exponent too large
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:
# result = result * base (tracking decimals)
res_clean = result.lstrip('0') or '0'
result = _str_mul(res_clean, base)
# Determine the resulting scale and apply it
res_scale = 0
base_scale = len(e) # rough tracking
# base = base * base
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 # integer required
n = x[1]
if n in ('0', '1'):
return '1'
n_int = int(n)
if n_int > 100000:
return None # too large
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