UNPKG

js-big-integer

Version:

Yet another class for arbitrary-precision integers in pure JavaScript. Small. Well tested.

845 lines (785 loc) 24 kB
/*jslint plusplus: true, vars: true, indent: 2*/ (function (global) { "use strict"; // BigInteger.js // Available under Public Domain // https://github.com/Yaffle/BigInteger/ // For implementation details, see "The Handbook of Applied Cryptography" // http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf var parseInteger = function (s, from, to, radix) { var i = from - 1; var n = 0; var y = radix < 10 ? radix : 10; while (++i < to) { var code = s.charCodeAt(i); var v = code - "0".charCodeAt(0); if (v < 0 || y <= v) { v = 10 - "A".charCodeAt(0) + code; if (v < 10 || radix <= v) { v = 10 - "a".charCodeAt(0) + code; if (v < 10 || radix <= v) { throw new RangeError(); } } } n = n * radix + v; } return n; }; var createArray = function (length) { var x = new Array(length); var i = -1; while (++i < length) { x[i] = 0; } return x; }; var epsilon = 2 / (9007199254740991 + 1); while (1 + epsilon / 2 !== 1) { epsilon /= 2; } var BASE = 2 / epsilon; var s = 134217728; while (s * s < 2 / epsilon) { s *= 2; } var SPLIT = s + 1; var BASELOG2 = Math.ceil(Math.log(BASE) / Math.log(2)); // Veltkamp-Dekker's algorithm // see http://web.mit.edu/tabbott/Public/quaddouble-debian/qd-2.3.4-old/docs/qd.pdf var fma = function (a, b, product) { var at = SPLIT * a; var ahi = at - (at - a); var alo = a - ahi; var bt = SPLIT * b; var bhi = bt - (bt - b); var blo = b - bhi; var error = ((ahi * bhi + product) + ahi * blo + alo * bhi) + alo * blo; return error; }; var fastTrunc = function (x) { var v = (x - BASE) + BASE; return v > x ? v - 1 : v; }; var performMultiplication = function (carry, a, b) { var product = a * b; var error = fma(a, b, -product); var hi = (product / BASE) - BASE + BASE; var lo = product - hi * BASE + error; if (lo >= 0) { lo -= BASE; hi += 1; } lo += carry; if (lo < 0) { lo += BASE; hi -= 1; } return {lo: lo, hi: hi}; }; var performDivision = function (a, b, divisor) { if (a >= divisor) { throw new RangeError(); } var p = a * BASE; var q = fastTrunc(p / divisor); var r = 0 - fma(q, divisor, -p); if (r < 0) { q -= 1; r += divisor; } r += b - divisor; if (r < 0) { r += divisor; } else { q += 1; } var y = fastTrunc(r / divisor); r -= y * divisor; q += y; return {q: q, r: r}; }; function BigInteger(sign, magnitude, length) { this.sign = sign; this.magnitude = magnitude; this.length = length; } var createBigInteger = function (sign, magnitude, length) { return new BigInteger(sign, magnitude, length); }; var fromHugeNumber = function (n) { var sign = n < 0 ? 1 : 0; var a = n < 0 ? 0 - n : 0 + n; if (a === 1 / 0) { throw new RangeError(); } console.assert(BASE === Math.pow(2, 53)); var i = 0; while (a >= Math.pow(BASE, 2)) { a /= BASE; i += 1; } var hi = Math.floor(a / BASE); var lo = a - hi * BASE; var digits = createArray(i + 2); digits[i + 1] = hi; digits[i + 0] = lo; return createBigInteger(sign, digits, i + 2); }; var fromNumber = function (n) { if (Math.floor(n) !== n) { throw new RangeError("Cannot convert " + n + " to BigInteger"); } if (n < BASE && 0 - n < BASE) { var a = createArray(1); a[0] = n < 0 ? 0 - n : 0 + n; return createBigInteger(n < 0 ? 1 : 0, a, n === 0 ? 0 : 1); } return fromHugeNumber(n); }; var fromString = function (s) { var length = s.length; if (length === 0) { throw new RangeError(); } var sign = 0; var signCharCode = s.charCodeAt(0); var from = 0; if (signCharCode === "+".charCodeAt(0)) { from = 1; } if (signCharCode === "-".charCodeAt(0)) { from = 1; sign = 1; } var radix = 10; if (from === 0 && length >= 2 && s.charCodeAt(0) === "0".charCodeAt(0)) { if (s.charCodeAt(1) === "b".charCodeAt(0)) { radix = 2; from = 2; } else if (s.charCodeAt(1) === "o".charCodeAt(0)) { radix = 8; from = 2; } else if (s.charCodeAt(1) === "x".charCodeAt(0)) { radix = 16; from = 2; } } length -= from; if (length === 0) { throw new RangeError(); } var groupLength = 0; var groupRadix = 1; var limit = fastTrunc(BASE / radix); while (groupRadix <= limit) { groupLength += 1; groupRadix *= radix; } var size = Math.floor((length - 1) / groupLength) + 1; var magnitude = createArray(size); var start = from + 1 + (length - 1 - (size - 1) * groupLength) - groupLength; var j = -1; while (++j < size) { var groupStart = start + j * groupLength; var c = parseInteger(s, (groupStart >= from ? groupStart : from), groupStart + groupLength, radix); var l = -1; while (++l < j) { var tmp = performMultiplication(c, magnitude[l], groupRadix); var lo = tmp.lo; var hi = tmp.hi; magnitude[l] = lo; c = hi; } magnitude[j] = c; } while (size > 0 && magnitude[size - 1] === 0) { size -= 1; } return createBigInteger(size === 0 ? 0 : sign, magnitude, size); }; // Math.pow(2, n) is slow in Chrome 93 function exp(x, n) { var a = 1; while (n !== 0) { var q = (n >> 1); if (n !== (q << 1)) { a *= x; } n = q; x *= x; } return a; } BigInteger.BigInt = function (x) { if (typeof x === "number") { return fromNumber(x); } if (typeof x === "string") { return fromString(x); } if (typeof x === "bigint") { return fromString(x.toString()); } if (x instanceof BigInteger) { return x; } if (typeof x === "boolean") { return fromNumber(Number(x)); } throw new RangeError(); }; BigInteger.asUintN = function (bits, bigint) { if (bits < 0) { throw new RangeError("bits argument should be non-negative"); } if (bigint.sign === 1) { var X = BigInteger.asUintN(bits, BigInteger.unaryMinus(bigint)); return BigInteger.equal(X, BigInteger.BigInt(0)) ? X : BigInteger.subtract(BigInteger.leftShift(BigInteger.BigInt(1), BigInteger.BigInt(bits)), X); } var n = Math.ceil(bits / BASELOG2); bits -= BASELOG2 * n; if (n > bigint.length) { return bigint; } var array = createArray(n); for (var i = 0; i < n; i += 1) { array[i] = bigint.magnitude[i]; } var m = exp(2, BASELOG2 + bits); array[n - 1] = array[n - 1] - Math.floor(array[n - 1] / m) * m; while (n >= 0 && array[n - 1] === 0) { n -= 1; } return createBigInteger(0, array, n); }; BigInteger.asIntN = function (bits, bigint) { if (bits === 0) { return BigInteger.BigInt(0); } var A = BigInteger.asUintN(bits, bigint); var X = BigInteger.leftShift(BigInteger.BigInt(1), BigInteger.BigInt(bits - 1)); return BigInteger.greaterThanOrEqual(A, X) ? BigInteger.subtract(A, BigInteger.add(X, X)) : A; }; BigInteger.toNumber = function (a) { if (a.length === 0) { return 0; } if (a.length === 1) { return a.sign === 1 ? 0 - a.magnitude[0] : a.magnitude[0]; } if (BASE + 1 !== BASE) { throw new RangeError(); } var x = a.magnitude[a.length - 1]; var y = a.magnitude[a.length - 2]; var i = a.length - 3; while (i >= 0 && a.magnitude[i] === 0) { i -= 1; } if (i >= 0 && (x !== 1 && y % 2 === 0 || x === 1 && y % 2 === 1)) { y += 1; } var z = (x * BASE + y) * exp(BASE, a.length - 2); return a.sign === 1 ? 0 - z : z; }; var compareMagnitude = function (a, b) { if (a === b) { return 0; } var c1 = a.length - b.length; if (c1 !== 0) { return c1 < 0 ? -1 : +1; } var i = a.length; while (--i >= 0) { var c = a.magnitude[i] - b.magnitude[i]; if (c !== 0) { return c < 0 ? -1 : +1; } } return 0; }; var compareTo = function (a, b) { var c = a.sign === b.sign ? compareMagnitude(a, b) : 1; return a.sign === 1 ? 0 - c : c; // positive zero will be returned for c === 0 }; var addAndSubtract = function (a, b, isSubtraction) { var z = compareMagnitude(a, b); var resultSign = z < 0 ? (isSubtraction !== 0 ? 1 - b.sign : b.sign) : a.sign; var min = z < 0 ? a : b; var max = z < 0 ? b : a; // |a| <= |b| if (min.length === 0) { return createBigInteger(resultSign, max.magnitude, max.length); } var subtract = 0; var resultLength = max.length; if (a.sign !== (isSubtraction !== 0 ? 1 - b.sign : b.sign)) { subtract = 1; if (min.length === resultLength) { while (resultLength > 0 && min.magnitude[resultLength - 1] === max.magnitude[resultLength - 1]) { resultLength -= 1; } } if (resultLength === 0) { // a === (-b) return createBigInteger(0, createArray(0), 0); } } // result !== 0 var result = createArray(resultLength + (1 - subtract)); var i = -1; var c = 0; while (++i < min.length) { var aDigit = min.magnitude[i]; c += max.magnitude[i] + (subtract !== 0 ? 0 - aDigit : aDigit - BASE); if (c < 0) { result[i] = BASE + c; c = 0 - subtract; } else { result[i] = c; c = 1 - subtract; } } i -= 1; while (++i < resultLength) { c += max.magnitude[i] + (subtract !== 0 ? 0 : 0 - BASE); if (c < 0) { result[i] = BASE + c; c = 0 - subtract; } else { result[i] = c; c = 1 - subtract; } } if (subtract === 0) { result[resultLength] = c; resultLength += c !== 0 ? 1 : 0; } else { while (resultLength > 0 && result[resultLength - 1] === 0) { resultLength -= 1; } } return createBigInteger(resultSign, result, resultLength); }; BigInteger.add = function (a, b) { return addAndSubtract(a, b, 0); }; BigInteger.subtract = function (a, b) { return addAndSubtract(a, b, 1); }; BigInteger.multiply = function (a, b) { if (a.length < b.length) { var tmp = a; a = b; b = tmp; } var alength = a.length; var blength = b.length; var am = a.magnitude; var bm = b.magnitude; var asign = a.sign; var bsign = b.sign; if (alength === 0 || blength === 0) { return createBigInteger(0, createArray(0), 0); } if (alength === 1 && am[0] === 1) { return createBigInteger(asign === 1 ? 1 - bsign : bsign, bm, blength); } if (blength === 1 && bm[0] === 1) { return createBigInteger(asign === 1 ? 1 - bsign : bsign, am, alength); } var astart = 0; while (am[astart] === 0) { // to optimize multiplications of a power of BASE astart += 1; } var resultSign = asign === 1 ? 1 - bsign : bsign; var resultLength = alength + blength; var result = createArray(resultLength); var i = -1; while (++i < blength) { var digit = bm[i]; if (digit !== 0) { // to optimize multiplications by a power of BASE var c = 0; var j = astart - 1; while (++j < alength) { var carry = 1; c += result[j + i] - BASE; if (c < 0) { c += BASE; carry = 0; } var tmp = performMultiplication(c, am[j], digit); var lo = tmp.lo; var hi = tmp.hi; result[j + i] = lo; c = hi + carry; } result[alength + i] = c; } } if (result[resultLength - 1] === 0) { resultLength -= 1; } return createBigInteger(resultSign, result, resultLength); }; var divideAndRemainder = function (a, b, isDivision) { if (b.length === 0) { throw new RangeError(); } if (a.length === 0) { return createBigInteger(0, createArray(0), 0); } var quotientSign = a.sign === 1 ? 1 - b.sign : b.sign; if (b.length === 1 && b.magnitude[0] === 1) { if (isDivision !== 0) { return createBigInteger(quotientSign, a.magnitude, a.length); } return createBigInteger(0, createArray(0), 0); } var divisorOffset = a.length + 1; // `+ 1` for extra digit in case of normalization var divisorAndRemainder = createArray(divisorOffset + b.length + 1); // `+ 1` to avoid `index < length` checks var divisor = divisorAndRemainder; var remainder = divisorAndRemainder; var n = -1; while (++n < a.length) { remainder[n] = a.magnitude[n]; } var m = -1; while (++m < b.length) { divisor[divisorOffset + m] = b.magnitude[m]; } var top = divisor[divisorOffset + b.length - 1]; // normalization var lambda = 1; if (b.length > 1) { lambda = fastTrunc(BASE / (top + 1)); if (lambda > 1) { var carry = 0; var l = -1; while (++l < divisorOffset + b.length) { var tmp = performMultiplication(carry, divisorAndRemainder[l], lambda); var lo = tmp.lo; var hi = tmp.hi; divisorAndRemainder[l] = lo; carry = hi; } divisorAndRemainder[divisorOffset + b.length] = carry; top = divisor[divisorOffset + b.length - 1]; } // assertion if (top < fastTrunc(BASE / 2)) { throw new RangeError(); } } var shift = a.length - b.length + 1; if (shift < 0) { shift = 0; } var quotient = undefined; var quotientLength = 0; // to optimize divisions by a power of BASE var lastNonZero = 0; while (divisor[divisorOffset + lastNonZero] === 0) { lastNonZero += 1; } var i = shift; while (--i >= 0) { var t = b.length + i; var q = BASE - 1; if (remainder[t] !== top) { var tmp2 = performDivision(remainder[t], remainder[t - 1], top); var q2 = tmp2.q; //var r2 = tmp2.r; q = q2; } var ax = 0; var bx = 0; var j = i - 1 + lastNonZero; while (++j <= t) { var tmp3 = performMultiplication(bx, q, divisor[divisorOffset + j - i]); var lo3 = tmp3.lo; var hi3 = tmp3.hi; bx = hi3; ax += remainder[j] - lo3; if (ax < 0) { remainder[j] = BASE + ax; ax = -1; } else { remainder[j] = ax; ax = 0; } } while (ax !== 0) { q -= 1; var c = 0; var k = i - 1 + lastNonZero; while (++k <= t) { c += remainder[k] - BASE + divisor[divisorOffset + k - i]; if (c < 0) { remainder[k] = BASE + c; c = 0; } else { remainder[k] = c; c = +1; } } ax += c; } if (isDivision !== 0 && q !== 0) { if (quotientLength === 0) { quotientLength = i + 1; quotient = createArray(quotientLength); } quotient[i] = q; } } if (isDivision !== 0) { if (quotientLength === 0) { return createBigInteger(0, createArray(0), 0); } return createBigInteger(quotientSign, quotient, quotientLength); } var remainderLength = a.length + 1; if (lambda > 1) { var r = 0; var p = remainderLength; while (--p >= 0) { var tmp4 = performDivision(r, remainder[p], lambda); var q4 = tmp4.q; var r4 = tmp4.r; remainder[p] = q4; r = r4; } if (r !== 0) { // assertion throw new RangeError(); } } while (remainderLength > 0 && remainder[remainderLength - 1] === 0) { remainderLength -= 1; } if (remainderLength === 0) { return createBigInteger(0, createArray(0), 0); } var result = createArray(remainderLength); var o = -1; while (++o < remainderLength) { result[o] = remainder[o]; } return createBigInteger(a.sign, result, remainderLength); }; BigInteger.divide = function (a, b) { return divideAndRemainder(a, b, 1); }; BigInteger.remainder = function (a, b) { return divideAndRemainder(a, b, 0); }; BigInteger.unaryMinus = function (a) { return createBigInteger(a.length === 0 ? a.sign : 1 - a.sign, a.magnitude, a.length); }; BigInteger.equal = function (a, b) { return compareTo(a, b) === 0; }; BigInteger.lessThan = function (a, b) { return compareTo(a, b) < 0; }; BigInteger.greaterThan = function (a, b) { return compareTo(a, b) > 0; }; BigInteger.notEqual = function (a, b) { return compareTo(a, b) !== 0; }; BigInteger.lessThanOrEqual = function (a, b) { return compareTo(a, b) <= 0; }; BigInteger.greaterThanOrEqual = function (a, b) { return compareTo(a, b) >= 0; }; BigInteger.exponentiate = function (a, b) { var n = BigInteger.toNumber(b); if (n < 0) { throw new RangeError(); } if (n > 9007199254740991) { var y = BigInteger.toNumber(a); if (y === 0 || y === -1 || y === +1) { return y === -1 && BigInteger.toNumber(BigInteger.remainder(b, BigInteger.BigInt(2))) === 0 ? BigInteger.unaryMinus(a) : a; } throw new RangeError(); } if (n === 0) { return BigInteger.BigInt(1); } if (a.length === 1 && (a.magnitude[0] === 2 || a.magnitude[0] === 16)) { var bits = Math.floor(Math.log(BASE) / Math.log(2) + 0.5); var abits = Math.floor(Math.log(a.magnitude[0]) / Math.log(2) + 0.5); var nn = abits * n; var q = Math.floor(nn / bits); var r = nn - q * bits; var array = createArray(q + 1); array[q] = Math.pow(2, r); return createBigInteger(a.sign === 0 || n % 2 === 0 ? 0 : 1, array, q + 1); } var x = a; while (n % 2 === 0) { n = Math.floor(n / 2); x = BigInteger.multiply(x, x); } var accumulator = x; n -= 1; if (n >= 2) { while (n >= 2) { var t = Math.floor(n / 2); if (t * 2 !== n) { accumulator = BigInteger.multiply(accumulator, x); } n = t; x = BigInteger.multiply(x, x); } accumulator = BigInteger.multiply(accumulator, x); } return accumulator; }; BigInteger.prototype.toString = function (radix) { if (radix == undefined) { radix = 10; } if (radix !== 10 && (radix < 2 || radix > 36 || radix !== Math.floor(radix))) { throw new RangeError("radix argument must be an integer between 2 and 36"); } // console.time(); var n = BigInteger.exponentiate(2**4, 2**16); console.timeEnd(); console.time(); n.toString(16).length; console.timeEnd(); if (this.length > 8 && true) { // https://github.com/GoogleChromeLabs/jsbi/blob/c9b179a4d5d34d35dd24cf84f7c1def54dc4a590/jsbi.mjs#L880 if (this.sign === 1) { return '-' + BigInteger.unaryMinus(this).toString(radix); } var s = Math.floor(this.length * Math.log(BASE) / Math.log(radix) / 2 + 0.5 - 1); var split = BigInteger.exponentiate(BigInteger.BigInt(radix), BigInteger.BigInt(s)); var q = BigInteger.divide(this, split); var r = BigInteger.subtract(this, BigInteger.multiply(q, split)); var a = r.toString(radix); return q.toString(radix) + '0'.repeat(s - a.length) + a; } var a = this; var result = a.sign === 1 ? "-" : ""; var remainderLength = a.length; if (remainderLength === 0) { return "0"; } if (remainderLength === 1) { result += a.magnitude[0].toString(radix); return result; } var groupLength = 0; var groupRadix = 1; var limit = fastTrunc(BASE / radix); while (groupRadix <= limit) { groupLength += 1; groupRadix *= radix; } // assertion if (groupRadix * radix <= BASE) { throw new RangeError(); } var size = remainderLength + Math.floor((remainderLength - 1) / groupLength) + 1; var remainder = createArray(size); var n = -1; while (++n < remainderLength) { remainder[n] = a.magnitude[n]; } var k = size; while (remainderLength !== 0) { var groupDigit = 0; var i = remainderLength; while (--i >= 0) { var tmp = performDivision(groupDigit, remainder[i], groupRadix); var q = tmp.q; var r = tmp.r; remainder[i] = q; groupDigit = r; } while (remainderLength > 0 && remainder[remainderLength - 1] === 0) { remainderLength -= 1; } k -= 1; remainder[k] = groupDigit; } result += remainder[k].toString(radix); while (++k < size) { var t = remainder[k].toString(radix); result += "0".repeat(groupLength - t.length) + t; } return result; }; var signedRightShift = function (x, n) { // (!) it should work fast if n ~ size(x) - 53 if (x.length === 0) { return x; } var shift = Math.floor(n / BASELOG2); var length = x.length - shift; if (length <= 0) { if (x.sign === 1) { var minusOne = createArray(1); minusOne[0] = 1; return createBigInteger(1, minusOne, 1); } return createBigInteger(0, createArray(0), 0); } var digits = createArray(length + (x.sign === 1 ? 1 : 0)); for (var i = 0; i < length; i += 1) { digits[i] = i + shift < 0 ? 0 : x.magnitude[i + shift]; } n -= shift * BASELOG2; var s = exp(2, n); var s1 = Math.floor(BASE / s); var pr = 0; for (var i = length - 1; i >= 0; i -= 1) { var q = Math.floor(digits[i] / s); var r = digits[i] - q * s; digits[i] = q + pr * s1; pr = r; } if (length >= 1 && digits[length - 1] === 0) { length -= 1; } if (x.sign === 1) { var hasRemainder = pr > 0; for (var i = 0; i < shift && !hasRemainder; i += 1) { hasRemainder = x.magnitude[i] !== 0; } if (hasRemainder) { if (length === 0) { length += 1; digits[0] = 1; } else { // subtract one var i = 0; while (i < length && digits[i] === BASE - 1) { digits[i] = 0; i += 1; } if (i < length) { digits[i] += 1; } else { length += 1; digits[i] = 1; } } } } return createBigInteger(x.sign, digits, length); }; BigInteger.signedRightShift = function (x, n) { return signedRightShift(x, BigInteger.toNumber(n)); }; BigInteger.leftShift = function (x, n) { return signedRightShift(x, 0 - BigInteger.toNumber(n)); }; BigInteger.prototype.valueOf = function () { //throw new TypeError(); console.error('BigInteger#valueOf is called'); return this; }; (global || globalThis).BigInteger = BigInteger; }(this));