eligendiexercitationem
Version:
Yet another class for arbitrary-precision integers in pure JavaScript. Small. Well tested.
888 lines (822 loc) • 23.5 kB
JavaScript
/*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 - 48;
if (v < 0 || y <= v) {
v = 10 - 65 + code;
if (v < 10 || radix <= v) {
v = 10 - 97 + 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;
// 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 = fastTrunc(product / BASE);
var lo = product - hi * BASE + error;
if (lo < 0) {
lo += BASE;
hi -= 1;
}
lo += carry - BASE;
if (lo < 0) {
lo += BASE;
} else {
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 BigIntegerInternal(sign, magnitude, length) {
this.sign = sign;
this.magnitude = magnitude;
this.length = length;
}
var createBigInteger = function (sign, magnitude, length) {
return new BigIntegerInternal(sign, magnitude, length);
};
var fromNumber = function (n) {
if (n >= BASE || 0 - n >= BASE) {
throw new RangeError();
}
var a = createArray(1);
a[0] = n < 0 ? 0 - n : 0 + n;
return createBigInteger(n < 0 ? 1 : 0, a, n === 0 ? 0 : 1);
};
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 === 43) { // "+"
from = 1;
}
if (signCharCode === 45) { // "-"
from = 1;
sign = 1;
}
var radix = 10;
if (from === 0 && s.length >= 2 && s.charCodeAt(0) === 48) {
if (s.charCodeAt(1) === 98) {
radix = 2;
from = 2;
} else if (s.charCodeAt(1) === 111) {
radix = 8;
from = 2;
} else if (s.charCodeAt(1) === 120) {
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);
};
BigIntegerInternal.BigInt = function (x) {
if (typeof x === "number") {
return fromNumber(x);
}
if (typeof x === "string") {
return fromString(x);
}
throw new RangeError();
};
BigIntegerInternal.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 && y % 2 === 1) {
y += 1;
}
var z = (x * BASE + y) * Math.pow(BASE, a.length - 2);
return a.sign === 1 ? 0 - z : z;
};
var compareMagnitude = function (a, b) {
if (a.length !== b.length) {
return a.length < b.length ? -1 : +1;
}
var i = a.length;
while (--i >= 0) {
if (a.magnitude[i] !== b.magnitude[i]) {
return a.magnitude[i] < b.magnitude[i] ? -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 < resultLength) {
var aDigit = i < min.length ? min.magnitude[i] : 0;
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;
}
}
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);
};
BigIntegerInternal.add = function (a, b) {
return addAndSubtract(a, b, 0);
};
BigIntegerInternal.subtract = function (a, b) {
return addAndSubtract(a, b, 1);
};
BigIntegerInternal.multiply = function (a, b) {
if (a.length === 0 || b.length === 0) {
return createBigInteger(0, createArray(0), 0);
}
if (a.length === 1 && a.magnitude[0] === 1) {
return createBigInteger(a.sign === 1 ? 1 - b.sign : b.sign, b.magnitude, b.length);
}
if (b.length === 1 && b.magnitude[0] === 1) {
return createBigInteger(a.sign === 1 ? 1 - b.sign : b.sign, a.magnitude, a.length);
}
var resultSign = a.sign === 1 ? 1 - b.sign : b.sign;
var resultLength = a.length + b.length;
var result = createArray(resultLength);
var i = -1;
while (++i < b.length) {
if (b.magnitude[i] !== 0) { // to optimize multiplications by a power of BASE
var c = 0;
var j = -1;
while (++j < a.length) {
var carry = 0;
c += result[j + i] - BASE;
if (c >= 0) {
carry = 1;
} else {
c += BASE;
}
var tmp = performMultiplication(c, a.magnitude[j], b.magnitude[i]);
var lo = tmp.lo;
var hi = tmp.hi;
result[j + i] = lo;
c = hi + carry;
}
result[a.length + i] = c;
}
}
while (resultLength > 0 && 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);
};
BigIntegerInternal.divide = function (a, b) {
return divideAndRemainder(a, b, 1);
};
BigIntegerInternal.remainder = function (a, b) {
return divideAndRemainder(a, b, 0);
};
BigIntegerInternal.unaryMinus = function (a) {
return createBigInteger(a.length === 0 ? a.sign : 1 - a.sign, a.magnitude, a.length);
};
BigIntegerInternal.equal = function (a, b) {
return compareTo(a, b) === 0;
};
BigIntegerInternal.lessThan = function (a, b) {
return compareTo(a, b) < 0;
};
BigIntegerInternal.greaterThan = function (a, b) {
return compareTo(a, b) > 0;
};
BigIntegerInternal.exponentiate = function (a, b) {
var n = BigIntegerInternal.toNumber(b);
if (n < 0) {
throw new RangeError();
}
if (n > 9007199254740991) {
var y = BigIntegerInternal.toNumber(a);
if (y === 0 || y === -1 || y === +1) {
return BigIntegerInternal.BigInt(y === -1 && BigIntegerInternal.toNumber(BigIntegerInternal.remainder(b, BigIntegerInternal.BigInt(2))) === 0 ? +1 : y);
}
throw new RangeError();
}
var accumulator = BigIntegerInternal.BigInt(1);
if (n > 0) {
var x = a;
while (n >= 2) {
var t = Math.floor(n / 2);
if (t * 2 !== n) {
accumulator = BigIntegerInternal.multiply(accumulator, x);
}
n = t;
x = BigIntegerInternal.multiply(x, x);
}
accumulator = BigIntegerInternal.multiply(accumulator, x);
}
return accumulator;
};
BigIntegerInternal.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");
}
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);
var j = groupLength - t.length;
while (--j >= 0) {
result += "0";
}
result += t;
}
return result;
};
function BigIntWrapper() {
}
BigIntWrapper.BigInt = function (x) {
return BigInt(x);
};
BigIntWrapper.toNumber = function (bigint) {
return Number(bigint);
};
BigIntWrapper.add = function (a, b) {
return a + b;
};
BigIntWrapper.subtract = function (a, b) {
return a - b;
};
BigIntWrapper.multiply = function (a, b) {
return a * b;
};
BigIntWrapper.divide = function (a, b) {
return a / b;
};
BigIntWrapper.remainder = function (a, b) {
return a % b;
};
BigIntWrapper.unaryMinus = function (a) {
return -a;
};
BigIntWrapper.equal = function (a, b) {
return a === b;
};
BigIntWrapper.lessThan = function (a, b) {
return a < b;
};
BigIntWrapper.greaterThan = function (a, b) {
return a > b;
};
BigIntWrapper.exponentiate = function (a, b) {
var n = BigIntWrapper.toNumber(b);
if (n < 0) {
throw new RangeError();
}
if (n > 9007199254740991) {
var y = BigIntWrapper.toNumber(a);
if (y === 0 || y === -1 || y === +1) {
return BigIntWrapper.BigInt(y === -1 && BigIntWrapper.toNumber(BigIntWrapper.remainder(b, BigIntWrapper.BigInt(2))) === 0 ? +1 : y);
}
throw new RangeError();
}
var accumulator = BigIntWrapper.BigInt(1);
if (n > 0) {
var x = a;
while (n >= 2) {
var t = Math.floor(n / 2);
if (t * 2 !== n) {
accumulator = BigIntWrapper.multiply(accumulator, x);
}
n = t;
x = BigIntWrapper.multiply(x, x);
}
accumulator = BigIntWrapper.multiply(accumulator, x);
}
return accumulator;
};
// noinline
var n = function (f) {
return function (x, y) {
return f(x, y);
};
};
var Internal = typeof BigInt !== "undefined" && BigInt(9007199254740991) + BigInt(2) - BigInt(2) === BigInt(9007199254740991) ? BigIntWrapper : BigIntegerInternal;
var toNumber = n(function (a) {
return Internal.toNumber(a);
});
var valueOf = function (x) {
if (typeof x === "number") {
return Internal.BigInt(x);
}
return x;
};
var toResult = function (x) {
var value = Internal.toNumber(x);
if (value >= -9007199254740991 && value <= +9007199254740991) {
return value;
}
return x;
};
var add = n(function (x, y) {
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.add(a, b));
});
var subtract = n(function (x, y) {
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.subtract(a, b));
});
var multiply = n(function (x, y) {
if (x === y) {
var c = valueOf(x);
return Internal.multiply(c, c);
}
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.multiply(a, b));
});
var divide = n(function (x, y) {
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.divide(a, b));
});
var remainder = n(function (x, y) {
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.remainder(a, b));
});
var exponentiate = n(function (x, y) {
var a = valueOf(x);
var b = valueOf(y);
return toResult(Internal.exponentiate(a, b));
});
var unaryMinus = n(function (x) {
var a = valueOf(x);
return Internal.unaryMinus(a);
});
var equal = n(function (x, y) {
if (typeof x === "number") {
return x === Internal.toNumber(y);
}
if (typeof y === "number") {
return Internal.toNumber(x) === y;
}
return Internal.equal(x, y);
});
var lessThan = n(function (x, y) {
if (typeof x === "number") {
return x < Internal.toNumber(y);
}
if (typeof y === "number") {
return Internal.toNumber(x) < y;
}
return Internal.lessThan(x, y);
});
var greaterThan = n(function (x, y) {
if (typeof x === "number") {
return x > Internal.toNumber(y);
}
if (typeof y === "number") {
return Internal.toNumber(x) > y;
}
return Internal.greaterThan(x, y);
});
function BigInteger() {
}
// Conversion from String:
// Conversion from Number:
BigInteger.BigInt = function (x) {
if (typeof x === "number") {
return x;
}
var value = 0 + Number(x);
if (value >= -9007199254740991 && value <= +9007199254740991) {
return value;
}
return Internal.BigInt(x);
};
// Conversion to Number:
BigInteger.toNumber = function (x) {
if (typeof x === "number") {
return x;
}
return toNumber(x);
};
// Arithmetic:
BigInteger.add = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
var value = x + y;
if (value >= -9007199254740991 && value <= +9007199254740991) {
return value;
}
}
return add(x, y);
};
BigInteger.subtract = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
var value = x - y;
if (value >= -9007199254740991 && value <= +9007199254740991) {
return value;
}
}
return subtract(x, y);
};
BigInteger.multiply = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
var value = 0 + x * y;
if (value >= -9007199254740991 && value <= +9007199254740991) {
return value;
}
}
return multiply(x, y);
};
BigInteger.divide = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
if (y !== 0) {
return x === 0 ? 0 : (x > 0 && y > 0) || (x < 0 && y < 0) ? 0 + Math.floor(x / y) : 0 - Math.floor((0 - x) / y);
}
}
return divide(x, y);
};
BigInteger.remainder = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
if (y !== 0) {
return 0 + x % y;
}
}
return remainder(x, y);
};
BigInteger.unaryMinus = function (x) {
if (typeof x === "number") {
return 0 - x;
}
return unaryMinus(x);
};
// Comparison:
BigInteger.equal = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
return x === y;
}
return equal(x, y);
};
BigInteger.lessThan = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
return x < y;
}
return lessThan(x, y);
};
BigInteger.greaterThan = function (x, y) {
if (typeof x === "number" && typeof y === "number") {
return x > y;
}
return greaterThan(x, y);
};
BigInteger.exponentiate = function (x, y) {
if (typeof x === "number" && typeof y === "number" && y >= 0 && y < 53) { // Math.log2(9007199254740991 + 1)
var value = 0 + Math.pow(x, y);
if (value >= -9007199254740991 && value <= 9007199254740991) {
return value;
}
}
return exponentiate(x, y);
};
global.BigInteger = BigInteger;
}(this));