| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Big Number Calculator</title> |
| <style> |
| * { box-sizing: border-box; margin: 0; padding: 0; } |
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; padding: 20px; } |
| .calc { background: #fff; border-radius: 12px; box-shadow: 0 2px 20px rgba(0,0,0,.1); padding: 30px; max-width: 720px; width: 100%; } |
| h1 { font-size: 24px; margin-bottom: 8px; color: #222; } |
| p.sub { color: #666; font-size: 14px; margin-bottom: 20px; } |
| .field { margin-bottom: 16px; } |
| .field label { display: block; font-weight: 600; font-size: 13px; color: #444; margin-bottom: 4px; } |
| .field textarea { width: 100%; padding: 10px 12px; font-size: 15px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; border: 1px solid #ccc; border-radius: 6px; resize: vertical; min-height: 40px; } |
| .field textarea:focus { outline: none; border-color: #4a90d9; box-shadow: 0 0 0 2px rgba(74,144,217,.2); } |
| .row { display: flex; gap: 12px; align-items: center; } |
| .row .field { flex: 1; margin-bottom: 12px; } |
| .precision { width: 80px; padding: 8px 10px; font-size: 14px; border: 1px solid #ccc; border-radius: 6px; text-align: center; } |
| .precision:focus { outline: none; border-color: #4a90d9; } |
| .ops { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 16px 0; } |
| .ops button { padding: 12px; font-size: 15px; font-weight: 600; border: 1px solid #ddd; border-radius: 6px; background: #fafafa; cursor: pointer; transition: all .15s; } |
| .ops button:hover { background: #e8f0fe; border-color: #4a90d9; } |
| .ops button:active { background: #d0e2fc; } |
| .ops button.op-add { background: #d4edda; border-color: #b7dfb9; } |
| .ops button.op-sub { background: #f8d7da; border-color: #f5c6cb; } |
| .ops button.op-mul { background: #d1ecf1; border-color: #bee5eb; } |
| .ops button.op-div { background: #fff3cd; border-color: #ffeaa7; } |
| .ops button.op-pow { background: #e2d1f9; border-color: #d0b8f0; } |
| .ops button.op-root { background: #fce4d6; border-color: #f8cbad; } |
| .ops button.op-fact { background: #ffd6e0; border-color: #ffb3c6; } |
| .result-label { font-weight: 600; font-size: 13px; color: #444; margin-bottom: 4px; } |
| .result { background: #f9f9fb; border: 1px solid #e0e0e0; border-radius: 6px; padding: 14px 16px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 14px; line-height: 1.5; word-break: break-all; white-space: pre-wrap; min-height: 50px; max-height: 300px; overflow-y: auto; } |
| .result .error { color: #d32f2f; } |
| .result .answer { color: #1a1a1a; } |
| .result .meta { color: #888; font-size: 12px; margin-top: 4px; } |
| @media (max-width: 600px) { .ops { grid-template-columns: repeat(3, 1fr); } .calc { padding: 16px; } } |
| </style> |
| </head> |
| <body> |
| <div class="calc"> |
| <h1>Big Number Calculator</h1> |
| <p class="sub">Integers, decimals, and scientific notation (e.g. 3.5e19). Arbitrary precision.</p> |
|
|
| <div class="row"> |
| <div class="field"> |
| <label for="x">X</label> |
| <textarea id="x" rows="2" placeholder="Enter first number..."></textarea> |
| </div> |
| <div class="field" id="y-field"> |
| <label for="y">Y</label> |
| <textarea id="y" rows="2" placeholder="Enter second number..."></textarea> |
| </div> |
| </div> |
|
|
| <div class="row" style="margin-bottom: 8px;"> |
| <label style="font-weight:600;font-size:13px;color:#444;margin-right:8px;">Precision:</label> |
| <input id="precision" class="precision" type="number" value="20" min="0" max="1000"> |
| <span style="font-size:13px;color:#666;">digits after decimal</span> |
| </div> |
|
|
| <div class="ops"> |
| <button class="op-add" onclick="calc('add')">X + Y</button> |
| <button class="op-sub" onclick="calc('sub')">X − Y</button> |
| <button class="op-mul" onclick="calc('mul')">X × Y</button> |
| <button class="op-div" onclick="calc('div')">X / Y</button> |
| <button class="op-pow" onclick="calc('pow')">X<sup>Y</sup></button> |
| <button class="op-root" onclick="calc('sqrt')">√X</button> |
| <button onclick="calc('square')">X²</button> |
| <button class="op-fact" onclick="calc('fact')">X!</button> |
| <button onclick="calc('mod')">X MOD Y</button> |
| <button onclick="calc('gcd')">GCD</button> |
| <button onclick="calc('lcm')">LCM</button> |
| </div> |
|
|
| <div class="result-label">Result</div> |
| <div id="result" class="result">Enter numbers and click an operation.</div> |
| </div> |
|
|
| <script> |
| |
| |
| |
| |
| function parseNumber(str) { |
| str = (str || '').trim().replace(/\s/g, ''); |
| if (!str) return null; |
| let sign = 1; |
| if (str[0] === '-') { sign = -1; str = str.slice(1); } |
| else if (str[0] === '+') { str = str.slice(1); } |
| if (!str || str === '.') return null; |
| |
| |
| let exp = 0; |
| const ei = str.search(/[eE]/); |
| if (ei >= 0) { |
| const eStr = str.slice(ei + 1); |
| if (!eStr || (eStr[0] !== '+' && eStr[0] !== '-' && !/^\d/.test(eStr))) return null; |
| exp = parseInt(eStr, 10); |
| if (isNaN(exp)) return null; |
| str = str.slice(0, ei); |
| } |
| if (!str || str === '.') return null; |
| |
| let intPart = '0', fracPart = ''; |
| const di = str.indexOf('.'); |
| if (di >= 0) { |
| intPart = str.slice(0, di).replace(/^0+/, '') || '0'; |
| fracPart = str.slice(di + 1).replace(/0+$/, ''); |
| } else { |
| intPart = str.replace(/^0+/, '') || '0'; |
| } |
| if (!/^\d+$/.test(intPart) || !/^\d*$/.test(fracPart)) return null; |
| |
| |
| if (exp > 0) { |
| const move = Math.min(exp, fracPart.length); |
| intPart += fracPart.slice(0, move); |
| fracPart = fracPart.slice(move); |
| exp -= move; |
| if (exp > 0) { |
| intPart += '0'.repeat(exp); |
| exp = 0; |
| } |
| } else if (exp < 0) { |
| exp = -exp; |
| const move = Math.min(exp, intPart.length - 1); |
| fracPart = intPart.slice(-move) + fracPart; |
| intPart = intPart.slice(0, -move) || '0'; |
| exp -= move; |
| if (exp > 0) { |
| fracPart = '0'.repeat(exp) + fracPart; |
| exp = 0; |
| } |
| } |
| |
| intPart = intPart.replace(/^0+/, '') || '0'; |
| fracPart = fracPart.replace(/0+$/, ''); |
| return { sign, intPart, fracPart }; |
| } |
| |
| function formatNumber(n) { |
| if (!n) return '0'; |
| let s = n.sign === -1 ? '-' : ''; |
| s += n.intPart; |
| if (n.fracPart) s += '.' + n.fracPart; |
| return s; |
| } |
| |
| function toBigInt(n, targetDec) { |
| |
| let s = n.intPart + (n.fracPart || '').padEnd(targetDec, '0'); |
| s = s.replace(/^0+/, '') || '0'; |
| return BigInt(s); |
| } |
| |
| function fromBigInt(val, decPlaces, precision, sign) { |
| |
| if (val === 0n) return '0'; |
| let s = val < 0n ? (-val).toString() : val.toString(); |
| const negative = (val < 0n) || (sign === -1); |
| |
| if (decPlaces <= 0) { |
| |
| if (decPlaces < 0) s += '0'.repeat(-decPlaces); |
| return (!negative ? '' : '-') + s; |
| } |
| |
| |
| while (s.length <= decPlaces) s = '0' + s; |
| const ins = s.length - decPlaces; |
| let intPart = s.slice(0, ins) || '0'; |
| let fracPart = s.slice(ins); |
| |
| |
| if (fracPart.length > precision) { |
| const roundDigit = +fracPart[precision]; |
| fracPart = fracPart.slice(0, precision); |
| if (roundDigit >= 5) { |
| |
| let bi = BigInt(intPart + fracPart) + 1n; |
| let newS = bi.toString(); |
| if (newS.length > intPart.length + fracPart.length) { |
| |
| intPart = newS.slice(0, newS.length - fracPart.length); |
| fracPart = newS.slice(newS.length - fracPart.length); |
| } else { |
| intPart = newS.slice(0, newS.length - fracPart.length) || '0'; |
| fracPart = newS.slice(newS.length - fracPart.length); |
| } |
| } |
| } |
| |
| fracPart = fracPart.replace(/0+$/, ''); |
| let result = intPart; |
| if (fracPart) result += '.' + fracPart; |
| return (!negative ? '' : '-') + result; |
| } |
| |
| |
| function getDec(a, b) { |
| return Math.max((a.fracPart || '').length, (b ? (b.fracPart || '').length : 0)); |
| } |
| |
| |
| |
| |
| function strCompare(a, b) { |
| |
| if (a.length !== b.length) return a.length > b.length ? 1 : -1; |
| return a === b ? 0 : (a > b ? 1 : -1); |
| } |
| |
| function strAdd(a, b) { |
| let carry = 0, r = ''; |
| let i = a.length - 1, j = b.length - 1; |
| while (i >= 0 || j >= 0 || carry) { |
| const da = i >= 0 ? +a[i--] : 0; |
| const db = j >= 0 ? +b[j--] : 0; |
| const s = da + db + carry; |
| r = (s % 10) + r; |
| carry = s / 10 | 0; |
| } |
| return r; |
| } |
| |
| function strSub(a, b) { |
| |
| let borrow = 0, r = ''; |
| let i = a.length - 1, j = b.length - 1; |
| while (i >= 0) { |
| const da = +a[i--]; |
| const db = j >= 0 ? +b[j--] : 0; |
| let d = da - db - borrow; |
| if (d < 0) { d += 10; borrow = 1; } else borrow = 0; |
| r = d + r; |
| } |
| return r.replace(/^0+/, '') || '0'; |
| } |
| |
| function strMul(a, b) { |
| if (a === '0' || b === '0') return '0'; |
| const res = Array(a.length + b.length).fill(0); |
| for (let i = a.length - 1; i >= 0; i--) { |
| for (let j = b.length - 1; j >= 0; j--) { |
| const p = +a[i] * +b[j] + res[i + j + 1]; |
| res[i + j + 1] = p % 10; |
| res[i + j] += p / 10 | 0; |
| } |
| } |
| return res.join('').replace(/^0+/, '') || '0'; |
| } |
| |
| function strDivMod(dividend, divisor) { |
| |
| if (divisor === '0') return null; |
| let quot = '', rem = ''; |
| for (let i = 0; i < dividend.length; i++) { |
| rem += dividend[i]; |
| rem = rem.replace(/^0+/, '') || '0'; |
| let count = 0; |
| while (strCompare(rem, divisor) >= 0) { |
| rem = strSub(rem, divisor); |
| count++; |
| } |
| quot += count; |
| } |
| return { quot: quot.replace(/^0+/, '') || '0', rem: rem || '0' }; |
| } |
| |
| function gcdStr(a, b) { |
| while (b !== '0') { |
| const result = strDivMod(a, b); |
| if (!result) return null; |
| a = b; |
| b = result.rem; |
| } |
| return a; |
| } |
| |
| |
| |
| function opAdd(x, y, prec) { |
| const dec = Math.max((x.fracPart || '').length, (y.fracPart || '').length); |
| const a = toBigInt(x, dec); |
| const b = toBigInt(y, dec); |
| const r = (x.sign === -1 ? -a : a) + (y.sign === -1 ? -b : b); |
| return fromBigInt(r, dec, prec); |
| } |
| |
| function opSub(x, y, prec) { |
| const dec = Math.max((x.fracPart || '').length, (y.fracPart || '').length); |
| const a = toBigInt(x, dec); |
| const b = toBigInt(y, dec); |
| const r = (x.sign === -1 ? -a : a) - (y.sign === -1 ? -b : b); |
| return fromBigInt(r, dec, prec); |
| } |
| |
| function opMul(x, y, prec) { |
| const decX = (x.fracPart || '').length; |
| const decY = (y.fracPart || '').length; |
| const a = toBigInt(x, 0); |
| const b = toBigInt(y, 0); |
| const r = (x.sign === -1 ? -a : a) * (y.sign === -1 ? -b : b); |
| return fromBigInt(r, decX + decY, prec); |
| } |
| |
| function opDiv(x, y, prec) { |
| if (y.intPart === '0' && !y.fracPart) return null; |
| const decX = (x.fracPart || '').length; |
| const decY = (y.fracPart || '').length; |
| const scale = decY + prec; |
| const a = toBigInt(x, scale); |
| const b = toBigInt(y, 0); |
| const r = (x.sign === -1 ? -a : a) / b; |
| const finalDec = scale - decX; |
| return fromBigInt(r, finalDec, prec, x.sign * y.sign); |
| } |
| |
| function opPow(x, y, prec) { |
| |
| |
| if (y.sign === -1) return '0'; |
| if (y.fracPart) { |
| |
| return opPowDecimal(x, y, prec); |
| } |
| |
| const expStr = y.intPart; |
| if (expStr === '0') return '1'; |
| |
| |
| const xStr = formatNumber(x); |
| const xSign = x.sign; |
| const xDec = (x.fracPart || '').length; |
| |
| |
| let result = '1'; |
| let base = xStr.replace(/^-/, ''); |
| let e = expStr; |
| |
| if (strCompare(e, '100000') > 0) { |
| return 'Result too large (exponent > 100000)'; |
| } |
| |
| while (e !== '0') { |
| |
| if (+e[e.length - 1] % 2 === 1) { |
| |
| const decB = (result.indexOf('.') >= 0 ? result.split('.')[1].length : 0); |
| const rClean = result.replace('.', ''); |
| const bDec = (base.indexOf('.') >= 0 ? base.split('.')[1].length : 0); |
| const bClean = base.replace('.', ''); |
| let prod = strMul(rClean, bClean); |
| let totalDec = decB + bDec; |
| while (prod.length <= totalDec) prod = '0' + prod; |
| const ins = prod.length - totalDec; |
| result = prod.slice(0, ins) + '.' + prod.slice(ins); |
| result = result.replace(/0+$/, ''); |
| if (result[result.length - 1] === '.') result = result.slice(0, -1); |
| result = result.replace(/^0+/, '') || '0'; |
| } |
| |
| { |
| const bDec = (base.indexOf('.') >= 0 ? base.split('.')[1].length : 0); |
| const bClean = base.replace('.', ''); |
| let sq = strMul(bClean, bClean); |
| let totalDec = bDec * 2; |
| while (sq.length <= totalDec) sq = '0' + sq; |
| const ins = sq.length - totalDec; |
| base = sq.slice(0, ins) + '.' + sq.slice(ins); |
| base = base.replace(/0+$/, ''); |
| if (base[base.length - 1] === '.') base = base.slice(0, -1); |
| base = base.replace(/^0+/, '') || '0'; |
| } |
| e = strDivMod(e, '2').quot; |
| } |
| |
| if (xSign === -1 && expStr !== '0' && expStr[expStr.length - 1] === '1') { |
| result = '-' + result; |
| } |
| |
| |
| if (result[0] === '-') { |
| const r = result.slice(1); |
| if (r.indexOf('.') >= 0) { |
| const [ip, fp] = r.split('.'); |
| let out = '-' + ip + '.' + fp.slice(0, prec); |
| out = out.replace(/0+$/, ''); |
| if (out[out.length - 1] === '.') out = out.slice(0, -1); |
| return out; |
| } |
| return '-' + r; |
| } else { |
| if (result.indexOf('.') >= 0) { |
| const [ip, fp] = result.split('.'); |
| let out = ip + '.' + fp.slice(0, prec); |
| out = out.replace(/0+$/, ''); |
| if (out[out.length - 1] === '.') out = out.slice(0, -1); |
| return out; |
| } |
| return result; |
| } |
| } |
| |
| function opPowDecimal(x, y, prec) { |
| |
| |
| const xVal = parseFloat(formatNumber(x)); |
| const yVal = parseFloat(formatNumber(y)); |
| if (xVal <= 0) return 'Requires X > 0 for decimal exponent'; |
| const result = Math.pow(xVal, yVal); |
| if (!isFinite(result)) return 'Overflow'; |
| return result.toPrecision(prec + 6).replace(/0+$/, '').replace(/\.$/, '').slice(0, prec + 10); |
| } |
| |
| function opSqrt(x, prec) { |
| if (x.sign === -1) return 'Negative input'; |
| const xDec = (x.fracPart || '').length; |
| const scale = prec * 2; |
| const a = toBigInt(x, scale); |
| const r = bigIntSqrt(a); |
| return fromBigInt(r, prec, prec, 1); |
| } |
| |
| function bigIntSqrt(n) { |
| if (n < 0n) return null; |
| if (n === 0n) return 0n; |
| let x = n; |
| let y = (x + 1n) >> 1n; |
| while (y < x) { |
| x = y; |
| y = (y + n / y) >> 1n; |
| } |
| return x; |
| } |
| |
| function opSquare(x, prec) { |
| return opMul(x, x, prec); |
| } |
| |
| function opFact(x, prec) { |
| if (x.sign === -1) return 'Negative input'; |
| if (x.fracPart) return 'Integer required'; |
| const n = x.intPart; |
| if (n === '0' || n === '1') return '1'; |
| |
| |
| const nNum = parseInt(n, 10); |
| if (nNum > 100000) return 'n > 100000 (result too large)'; |
| |
| let result = '1'; |
| for (let i = 2n; i <= BigInt(n); i++) { |
| result = strMul(result, i.toString()); |
| |
| if (i % 10000n === 0n) { |
| |
| } |
| } |
| return result; |
| } |
| |
| function opMod(x, y, prec) { |
| if (y.intPart === '0' && !y.fracPart) return 'Division by zero'; |
| |
| const a = BigInt(x.sign === -1 ? '-' + x.intPart : x.intPart); |
| const b = BigInt(y.sign === -1 ? '-' + y.intPart : y.intPart); |
| if (b === 0n) return 'Division by zero'; |
| return (a % b).toString(); |
| } |
| |
| function opGcd(x, y) { |
| |
| const a = x.intPart.replace(/^0+/, '') || '0'; |
| const b = y.intPart.replace(/^0+/, '') || '0'; |
| if (a === '0' && b === '0') return '0'; |
| if (a === '0') return b; |
| if (b === '0') return a; |
| return gcdStr(a, b); |
| } |
| |
| function opLcm(x, y, prec) { |
| const a = x.intPart.replace(/^0+/, '') || '0'; |
| const b = y.intPart.replace(/^0+/, '') || '0'; |
| if (a === '0' || b === '0') return '0'; |
| const g = gcdStr(a, b); |
| if (!g) return 'Error'; |
| |
| const prod = strMul(a, b); |
| return strDivMod(prod, g).quot; |
| } |
| |
| |
| |
| function showY(show) { |
| document.getElementById('y-field').style.display = show ? 'block' : 'none'; |
| document.getElementById('y').disabled = !show; |
| } |
| |
| let lastOp = ''; |
| |
| function calc(op) { |
| const resultDiv = document.getElementById('result'); |
| const xStr = document.getElementById('x').value; |
| const yStr = document.getElementById('y').value; |
| const prec = parseInt(document.getElementById('precision').value, 10) || 20; |
| |
| |
| const singleOps = ['sqrt', 'square', 'fact']; |
| const hideY = singleOps.includes(op); |
| showY(!hideY); |
| lastOp = op; |
| |
| const x = parseNumber(xStr); |
| if (hideY) { |
| if (!x) { |
| resultDiv.innerHTML = '<span class="error">Invalid number X</span>'; |
| return; |
| } |
| let result; |
| switch (op) { |
| case 'sqrt': result = opSqrt(x, prec); break; |
| case 'square': result = opSquare(x, prec); break; |
| case 'fact': result = opFact(x, prec); break; |
| } |
| displayResult(result, op, xStr, '', prec); |
| return; |
| } |
| |
| |
| const y = parseNumber(yStr); |
| if (!x || !y) { |
| resultDiv.innerHTML = '<span class="error">Invalid number</span>'; |
| return; |
| } |
| |
| let result; |
| switch (op) { |
| case 'add': result = opAdd(x, y, prec); break; |
| case 'sub': result = opSub(x, y, prec); break; |
| case 'mul': result = opMul(x, y, prec); break; |
| case 'div': result = opDiv(x, y, prec); break; |
| case 'pow': result = opPow(x, y, prec); break; |
| case 'mod': result = opMod(x, y, prec); break; |
| case 'gcd': result = opGcd(x, y); break; |
| case 'lcm': result = opLcm(x, y, prec); break; |
| default: result = 'Unknown operation'; |
| } |
| displayResult(result, op, xStr, yStr, prec); |
| } |
| |
| function displayResult(result, op, xStr, yStr, prec) { |
| const div = document.getElementById('result'); |
| if (result === null) { |
| div.innerHTML = '<span class="error">Error: division by zero</span>'; |
| return; |
| } |
| |
| const opLabels = { |
| add: 'X + Y', sub: 'X − Y', mul: 'X × Y', div: 'X / Y', |
| pow: 'X^Y', sqrt: '√X', square: 'X²', fact: 'X!', |
| mod: 'X MOD Y', gcd: 'GCD', lcm: 'LCM' |
| }; |
| |
| const digitCount = result.replace(/^-/, '').replace(/\./g, '').length; |
| const output = String(result).length > 500 |
| ? '<span class="answer">' + result.slice(0, 200) + '…' + result.slice(-50) + '</span>' |
| : '<span class="answer">' + result + '</span>'; |
| |
| div.innerHTML = output + |
| '<div class="meta">' + opLabels[op] + ' · ' + digitCount + ' digits' + |
| (String(result).length > 500 ? ' (truncated, ' + String(result).length + ' chars)' : '') + '</div>'; |
| } |
| |
| |
| document.addEventListener('keydown', function(e) { |
| if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { |
| e.preventDefault(); |
| if (lastOp) calc(lastOp); |
| } |
| }); |
| </script> |
| </body> |
| </html> |
|
|