bigint-gcd
Version:
greater common divisor (gcd) of two BigInt values using Lehmer's GCD algorithm
661 lines (612 loc) • 19.8 kB
JavaScript
/*jshint esversion:11*/
const SUBQUADRATIC_GCD_THRESHOLD = 32 * 1024;
const SUBQUADRATIC_HALFGCD_THRESHOLD = 4096;
const DOUBLE_DIGIT_METHOD = true;
const USE_HALF_EXTENDED = true;
let SMALL_GCD_MAX = 0n;
let DIGITSIZE = 0;
let smallgcd = null;
let smallgcdext = null;
let wasmHelper = null;
let jsHelper = null;
if (true && typeof WebAssembly !== 'undefined') {
const wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,20,3,96,0,1,126,96,2,126,126,1,126,96,5,126,126,126,126,127,1,127,3,8,7,1,1,2,0,0,0,0,6,21,4,126,1,66,0,11,126,1,66,0,11,126,1,66,0,11,126,1,66,0,11,7,47,7,6,117,54,52,103,99,100,0,0,9,117,54,52,103,99,100,101,120,116,0,1,6,104,101,108,112,101,114,0,2,1,65,0,3,1,66,0,4,1,67,0,5,1,68,0,6,10,141,4,7,37,1,1,126,32,1,66,0,82,4,64,3,64,32,0,32,1,130,33,2,32,1,33,0,32,2,34,1,66,0,82,13,0,11,11,32,0,11,97,1,6,126,66,1,33,6,66,1,33,3,32,1,66,0,82,4,64,3,64,32,0,32,0,32,1,128,34,7,32,1,126,125,33,4,32,1,33,0,32,6,32,2,32,7,126,125,33,1,32,5,32,3,32,7,126,125,33,7,32,2,33,6,32,3,33,5,32,1,33,2,32,7,33,3,32,4,34,1,66,0,82,13,0,11,11,32,6,36,0,32,5,36,1,32,0,11,238,2,2,7,126,2,127,66,1,33,9,66,1,33,7,32,0,66,127,82,32,2,66,127,82,113,4,64,3,64,32,0,32,6,124,33,8,32,0,32,9,124,33,6,32,2,32,5,124,33,5,32,2,32,7,124,33,7,32,12,65,1,113,4,64,32,8,33,9,32,6,33,8,32,9,33,6,32,5,33,9,32,7,33,5,32,9,33,7,11,3,64,32,5,32,6,32,8,32,7,128,34,11,32,5,126,125,34,9,86,4,64,32,12,65,1,106,33,12,32,8,32,7,32,11,126,125,33,10,32,0,32,2,32,11,126,125,33,11,32,5,33,8,32,7,33,6,32,2,33,0,32,10,33,5,32,9,33,7,32,11,33,2,12,1,11,11,32,6,32,0,125,33,9,32,8,32,0,125,33,6,32,5,32,2,125,33,5,32,7,32,2,125,33,7,32,12,65,1,113,4,64,32,9,33,8,32,6,33,9,32,8,33,6,32,5,33,8,32,7,33,5,32,8,33,7,11,32,4,32,0,32,9,124,34,8,32,0,32,6,124,34,10,32,8,32,10,86,27,121,167,34,13,32,4,32,13,72,27,34,13,4,64,32,1,32,1,32,4,32,13,107,34,4,172,34,8,136,34,10,32,8,134,125,33,1,32,3,32,3,32,8,136,34,11,32,8,134,125,33,3,32,9,32,10,126,32,6,32,11,126,124,32,0,32,13,172,34,8,134,124,33,0,32,5,32,10,126,32,7,32,11,126,124,32,2,32,8,134,124,33,2,11,32,13,13,0,11,11,32,9,36,0,32,6,36,1,32,5,36,2,32,7,36,3,65,0,11,4,0,35,0,11,4,0,35,1,11,4,0,35,2,11,4,0,35,3,11]);
try {
const exports = new WebAssembly.Instance(new WebAssembly.Module(wasmCode)).exports;
if (exports.helper(1n, 0n, 1n, 0n) != null) {
wasmHelper = function (x, xlo, y, ylo, lobits) {
exports.helper(x, xlo, y, ylo, lobits);
return [exports.A(), exports.B(), exports.C(), exports.D()];
};
DIGITSIZE = 64;
SMALL_GCD_MAX = BigInt.asUintN(64, -1n);
}
if (exports.u64gcd(0n, 0n) === 0n) {
smallgcd = exports.u64gcd;
}
if (exports.u64gcdext(0n, 0n) === 0n) {
smallgcdext = function (a, b) {
const g = exports.u64gcdext(a, b);
return [exports.A(), exports.B(), g];
};
}
} catch (error) {
console.log(error);
}
}
function AsmModule(stdlib) {
"use asm";
var floor = stdlib.Math.floor;
var max = stdlib.Math.max;
var clz32 = stdlib.Math.clz32;
var gA = -0.0;
var gB = -0.0;
var gC = -0.0;
var gD = -0.0;
function f64gcd(a, b) {
a = +a;
b = +b;
var b1 = -0.0;
var q = -0.0;
while (b > -0.0) {
q = +floor(a / b);
b1 = a - q * b;
a = b;
b = b1;
}
return +a;
}
function f64gcdext(a, b) {
a = +a;
b = +b;
var b1 = -0.0;
var q = -0.0;
var A = 1.0;
var B = -0.0;
var C = -0.0;
var D = 1.0;
var C1 = -0.0;
var D1 = -0.0;
while (b > -0.0) {
q = +floor(a / b);
b1 = a - q * b;
a = b;
b = b1;
C1 = A - q * C;
D1 = B - q * D;
A = C;
B = D;
C = C1;
D = D1;
}
gA = A;
gB = B;
return +a;
}
// 1 + floor(log2(x))
function log2(x) {
x = +x;
var e = 0;
while (x >= 4294967296.0) {
x = x * 2.3283064365386963e-10;
e = (e + 32) | 0;
}
e = (e + (32 - (clz32(~~x) | 0))) | 0;
return e | 0;
}
// 2**n
function exp2(n) {
n = n | 0;
var result = 1.0;
while ((n | 0) < 0) {
n = (n + 32) | 0;
result = result * 2.3283064365386963e-10; // * 2**-32
}
while ((n | 0) >= 32) {
n = (n - 32) | 0;
result = result * 4294967296.0; // * 2**32
}
result = result * +((1 << n) >>> 0);
return +result;
}
// @Deprecated, see helper64.js
function jsHelper(x, xlo, y, ylo, lobits) {
x = +x;
xlo = +xlo;
y = +y;
ylo = +ylo;
lobits = lobits | 0;
var A = 1.0;
var B = -0.0;
var C = -0.0;
var D = 1.0;
var bits = 0;
var sameQuotient = 0;
var q = -0.0;
var y1 = -0.0;
var A1 = -0.0;
var B1 = -0.0;
var C1 = -0.0;
var D1 = -0.0;
var b = 0;
var d = -0.0;
var dInv = -0.0;
var xlo1 = -0.0;
var ylo1 = -0.0;
var p = -0.0;
if (y != -0.0) {
do {
do {
q = floor(x / y);
y1 = x - q * y;
A1 = C;
B1 = D;
C1 = A - q * C;
D1 = B - q * D;
// The quotient for a point (x_initial + alpha, y_initial + beta), where 0 <= alpha < 1 and 0 <= beta < 1:
// floor((x + A * alpha + B * beta) / (y + C * alpha + D * beta))
// As the sign(A) === -sign(B) === -sign(C) === sign(D) (ignoring zero entries) the maximum and minimum values are floor((x + A) / (y + C)) and floor((x + B) / (y + D))
// floor((x + A) / (y + C)) === q <=> 0 <= (x + A) - q * (y + C) < (y + C) <=> 0 <= y1 + C1 < y + C
// floor((x + B) / (y + D)) === q <=> 0 <= (x + B) - q * (y + D) < (y + D) <=> 0 <= y1 + D1 < y + D
sameQuotient = (-0.0 <= y1 + C1) & (y1 + C1 < y + C) &
(-0.0 <= y1 + D1) & (y1 + D1 < y + D);
if (sameQuotient) {
x = y;
y = y1;
A = A1;
B = B1;
C = C1;
D = D1;
}
} while (sameQuotient);
b = (53 - (log2(x + max(A, B)) | 0)) | 0;
bits = (b | 0) < 0 ? b : ((b | 0) > (lobits | 0) ? lobits : b);
if ((b | 0) != 0) {
d = +exp2((lobits - bits) | 0);
dInv = +exp2((bits - lobits) | 0);
xlo1 = +floor(xlo * dInv);
ylo1 = +floor(ylo * dInv);
xlo = xlo - xlo1 * d;
ylo = ylo - ylo1 * d;
lobits = (lobits - bits) | 0;
p = +exp2(bits);
x = A * xlo1 + B * ylo1 + x * p;
y = C * xlo1 + D * ylo1 + y * p;
}
} while ((bits | 0) != 0);
}
gA = A;
gB = B;
gC = C;
gD = D;
return 0;
}
function A() {
return gA;
}
function B() {
return gB;
}
function C() {
return gC;
}
function D() {
return gD;
}
return {f64gcdext: f64gcdext, f64gcd: f64gcd, helper: jsHelper, A: A, B: B, C: C, D: D};
}
if (DIGITSIZE === 0) {
const asmExports = AsmModule(globalThis);
jsHelper = function (x, xlo, y, ylo, lobits) {
asmExports.helper(x, xlo, y, ylo, lobits);
return [BigInt(asmExports.A()), BigInt(asmExports.B()), BigInt(asmExports.C()), BigInt(asmExports.D())];
};
DIGITSIZE = 53;
SMALL_GCD_MAX = BigInt.asUintN(53, -1n);
smallgcd = function (a, b) {
return BigInt(asmExports.f64gcd(Number(BigInt(a)), Number(BigInt(b))));
};
smallgcdext = function (a, b) {
const g = asmExports.f64gcdext(Number(BigInt(a)), Number(BigInt(b)));
return [BigInt(asmExports.A()), BigInt(asmExports.B()), BigInt(g)];
};
}
// https://github.com/tc39/proposal-bigint/issues/205
// https://github.com/tc39/ecma262/issues/1729
// floor(log2(a)) + 1 if a > 0
function bitLength(a) {
const s = a.toString(16);
const c = s.charCodeAt(0) - 0 - '0'.charCodeAt(0);
if (c <= 0) {
throw new RangeError();
}
return (s.length - 1) * 4 + (32 - Math.clz32(Math.min(c, 8)));
}
const frexpf64 = typeof Float64Array !== 'undefined' ? new Float64Array(1) : null;
const frexpi32 = typeof Float64Array !== 'undefined' ? new Int32Array(frexpf64.buffer) : null;
let previousValue = 0;
// some terrible optimization as bitLength is slow
function bitLength2(a) {
if (previousValue <= 1024) {
const n = -0.0 + Number(BigInt(a));
if (frexpf64 != null) {
frexpf64[0] = n;
const e = (frexpi32[1] >> 20) - 1023;
if (e < 1024 && frexpi32[0] !== 0 || (frexpi32[1] & 0xFFFFF) !== 0) {
previousValue = e + 1;
return previousValue;
}
}
const x = Math.log2(n) + 1024 * 4 - 1024 * 4;
const y = Math.ceil(x);
if (x !== y) {
previousValue = y;
return previousValue;
}
}
if (previousValue < DIGITSIZE) {
previousValue = DIGITSIZE;
}
const n = -0.0 + Number(a >> BigInt(previousValue - DIGITSIZE));
if (n >= 1 && n <= 9007199254740992) { // 2**53
let x = +n;
let e = 0;
while (x > +(1 << 30)) {
x = Math.floor(x / +(1 << 30));
e += 30;
}
e += (32 - Math.clz32(x));
previousValue = previousValue - DIGITSIZE + e;
return previousValue;
}
previousValue = bitLength(a);
return previousValue;
}
function helper(X, Y) {
if (typeof X !== 'bigint' || typeof Y !== 'bigint') {
throw new TypeError();
}
if (wasmHelper != null) {
if (DIGITSIZE !== 64) {
throw new RangeError();
}
if (!DOUBLE_DIGIT_METHOD) {
return wasmHelper(X, 0n, Y, 0n, 0);
}
const x = BigInt.asUintN(64, X >> 64n);
const xlo = BigInt.asUintN(64, X);
const y = BigInt.asUintN(64, Y >> 64n);
const ylo = BigInt.asUintN(64, Y);
return wasmHelper(x, xlo, y, ylo, 64);
} else {
if (DIGITSIZE !== 53) {
throw new RangeError();
}
if (!DOUBLE_DIGIT_METHOD) {
return jsHelper(-0.0 + Number(X), 0, -0.0 + Number(Y), 0, 0);
}
const x = -0.0 + Number(X >> 53n);
const xlo = -0.0 + Number(BigInt.asUintN(53, X));
const y = -0.0 + Number(Y >> 53n);
const ylo = -0.0 + Number(BigInt.asUintN(53, Y));
return jsHelper(x, xlo, y, ylo, 53);
}
}
//TODO: remove those comments:
// floor((a + 1) / b) < q = floor(a / b) < floor(a / (b + 1))
// ([A, B], [C, D]) * (a + x, b + y) = (A*(a+x)+B*(b+y), C*(a+x)+D*(b+y)) = (A*a+B*b, C*a+D*b) + (A*x+B*y, C*x+D*y)
//console.assert(A * D >= 0 && B * C >= 0 && A * B <= 0 && D * C <= 0);//TODO: why - ?
//const [a1, b1] = [a + A, b + C]; // T * (a_initial + 1n, b_initial);
//const [a2, b2] = [a + B, b + D]; // T * (a_initial, b_initial + 1n);
//if (!isSmall && n <= size * (2 / 3)) { // TODO: ?, the constant is based on some testing with some example
// return [A, B, C, D, a, b];
//}
function abs(a) {
if (typeof a !== 'bigint') {
throw new TypeError();
}
return a < 0n ? -a : a;
}
function max(a, b) {
if (typeof a !== 'bigint' || typeof b !== 'bigint') {
throw new TypeError();
}
return a < b ? b : a;
}
function halfgcd(a, b, extended = true, reallyhalfgcd = true, wrapper = false) {
if (typeof a !== 'bigint' || typeof b !== 'bigint') {
throw new TypeError();
}
extended = extended || reallyhalfgcd;
// 2x2 matrix:
let A = 1n;
let B = 0n;
let C = 0n;
let D = 1n;
let step = 0;
//TODO: TEST
if (a < 0n) {
a = -a;
if (extended) {
A = -A;
B = -B;
step += 1;
}
}
//TODO: TEST
if (b < 0n) {
b = -b;
if (extended) {
C = -C;
D = -D;
step += 1;
}
}
if (a < b) {
const tmp = a;
a = b;
b = tmp;
if (extended) {
const C1 = A;
A = C;
C = C1;
const D1 = B;
B = D;
D = D1;
step += 1;
}
}
// The function calculates the transformation matrix for numbers (x, y), where a <= x < a + 1 and b <= y < b + 1
// Seems, this definition is not the same as in https://mathworld.wolfram.com/Half-GCD.html
// Note: for debugging it is useful to compare the quotients in the simple Euclidean algorithm vs the quotients there
// see helper64.js for the properties used
let isSmall = false;
if (reallyhalfgcd) {
if (BigInt.asUintN(SUBQUADRATIC_HALFGCD_THRESHOLD, a) === a) {
isSmall = true;
}
}
let isVerySmall = false;
let sizea0 = 0;
while ((reallyhalfgcd || a > SMALL_GCD_MAX) && b !== 0n) {
//console.assert(a >= b);
if (!isSmall) {
// Subquadratic Lehmer's algorithm:
if (!reallyhalfgcd) {
if (BigInt.asUintN(SUBQUADRATIC_GCD_THRESHOLD * (extended ? 1 / 16 : 1), b) === b) {
isSmall = true;
continue;
}
}
}
step += 1;
if (!isSmall) {
if (reallyhalfgcd && step === 1) {
sizea0 = bitLength(a);
}
const s1 = reallyhalfgcd ? bitLength(max(abs(C), abs(D))) : 0;
let m = reallyhalfgcd ? Math.max(0, Math.ceil((sizea0 - s1 - s1) * (1 / 2))) : (extended ? 0 : Math.floor(bitLength(a) * 2 / 3)); // 2/3 is somehow faster
let M = BigInt(m);
if (step !== 1 && reallyhalfgcd) {
if (m < 256) {
//if (s1 / sizea0 < 0.46) {
// console.debug(s1 / sizea0);
//}
isSmall = true;
continue;
}
//if (s1 / sizea0 > 0.251) {
// console.debug(s1 / sizea0);
//}
let k = 8;
while (((a + A) >> M) !== ((a + B) >> M) ||
((b + C) >> M) !== ((b + D) >> M)) {
// min and max values have different high bits
//isSmall = true;
//continue;
m += k;
M = BigInt(m);
k += k;
}
}
const [$A1, $B1, $C1, $D1, $ahi1, $bhi1] = halfgcd(a >> M, b >> M);
const [A1, B1, C1, D1, ahi1, bhi1] = [BigInt($A1), BigInt($B1), BigInt($C1), BigInt($D1), BigInt($ahi1), BigInt($bhi1)];
//if (typeof A1 !== 'bigint' || typeof B1 !== 'bigint' || typeof C1 !== 'bigint' || typeof D1 !== 'bigint' || typeof ahi1 !== 'bigint' || typeof bhi1 !== 'bigint') {
//throw new TypeError();
//}
if (B1 !== 0n) {
if (extended) {
// T := T1 * T:
if (step === 1) {
A = A1;
B = B1;
C = C1;
D = D1;
} else {
const B2 = A1 * B + B1 * D;
const D2 = C1 * B + D1 * D;
B = B2;
D = D2;
if (!USE_HALF_EXTENDED || reallyhalfgcd) {
const A2 = A1 * A + B1 * C;
const C2 = C1 * A + D1 * C;
A = A2;
C = C2;
}
}
}
const alo = BigInt.asUintN(m, a);
const blo = BigInt.asUintN(m, b);
// (a, b) := T1 * (alo, blo) + T1 * (ahi, bhi) * 2**m:
const a1 = (A1 * alo + B1 * blo) + (ahi1 << M);
const b1 = (C1 * alo + D1 * blo) + (bhi1 << M);
a = a1;
b = b1;
if (a < 0n || b < 0n) {
throw new TypeError("assertion");
}
continue;
} else {
//console.assert(A1 === 1n && B1 === 0n && C1 === 0n && D1 === 1n);
}
}
if (isSmall && !isVerySmall) {
// Lehmer's algorithm:
let m = Math.max(0, bitLength2(a) - DIGITSIZE * (DOUBLE_DIGIT_METHOD ? 2 : 1));
let M = m === 0 ? 0n : BigInt(m);
if (step !== 1 && reallyhalfgcd) {
if (((a + A) >> M) !== ((a + B) >> M) ||
((b + C) >> M) !== ((b + D) >> M)) {
// min and max values have different high bits
if (!wrapper) {
break;
}
do {
m += 8;
M = BigInt(m);
} while (((a + A) >> M) !== ((a + B) >> M) ||
((b + C) >> M) !== ((b + D) >> M));
if ((b >> M) === 0n) {
isVerySmall = true;
continue;
}
}
}
const [$A1, $B1, $C1, $D1] = helper((m === 0 ? a : a >> M), (m === 0 ? b : b >> M));
const [A1, B1, C1, D1] = [BigInt($A1), BigInt($B1), BigInt($C1), BigInt($D1)];
//if (typeof A1 !== 'bigint' || typeof B1 !== 'bigint' || typeof C1 !== 'bigint' || typeof D1 !== 'bigint') {
//throw new TypeError();
//}
if (B1 !== 0n) {
if (extended) {
// T := T1 * T:
if (step === 1) {
A = A1;
B = B1;
C = C1;
D = D1;
} else {
const B2 = A1 * B + B1 * D;
const D2 = C1 * B + D1 * D;
B = B2;
D = D2;
if (!USE_HALF_EXTENDED || reallyhalfgcd) {
const A2 = A1 * A + B1 * C;
const C2 = C1 * A + D1 * C;
A = A2;
C = C2;
}
}
}
// (a, b) := T1 * (a, b):
const a1 = A1 * a + B1 * b;
const b1 = C1 * a + D1 * b;
a = a1;
b = b1;
if (a < 0n || b < 0n) {
throw new TypeError("assertion");
}
continue;
} else {
//console.assert(A1 === 1n && B1 === 0n && C1 === 0n && D1 === 1n);
}
}
//console.debug('%');
const q = a / b;
const b1 = a - q * b;
if (extended) {
const C1 = A - q * C;
const D1 = B - q * D;
if (reallyhalfgcd) {
const sameQuotient = 0n <= b1 + C1 && b1 + C1 < b + C &&
0n <= b1 + D1 && b1 + D1 < b + D;
if (!sameQuotient) {
break;
}
}
// T := {{0, 1}, {1, -q}} * T:
A = C;
B = D;
C = C1;
D = D1;
}
// (a, b) := {{0, 1}, {1, -q}} * (a, b)
a = b;
b = b1;
//gcd.debug(q);
}
// see "2. General structure of subquadratic gcd algorithms" in “On Schönhage’s algorithm and subquadratic integer GCD computation” by Möller
return [A, B, C, D, a, b]; // for performance transformedA and transformedB are returned
}
// https://en.wikipedia.org/wiki/Lehmer%27s_GCD_algorithm
// https://www.imsc.res.in/~kapil/crypto/notes/node11.html
function LehmersGCD(a, b) {
const [A1, B1, C1, D1, a1, b1] = halfgcd(a, b, false, false);
a = BigInt(a1);
b = BigInt(b1);
if (b !== 0n) {
a = BigInt.asUintN(64, smallgcd(a, b));
}
return a;
}
function LehmersGCDExt(a, b) {
const [A1, B1, C1, D1, a1, b1] = halfgcd(a, b, true, false);
const a0 = a;
const b0 = b;
let A = BigInt(A1);
let B = BigInt(B1);
let C = BigInt(C1);
let D = BigInt(D1);
a = BigInt(a1);
b = BigInt(b1);
if (b1 !== 0n) {
const [A1, B1, g] = smallgcdext(a, b);
a = BigInt.asUintN(64, g);
b = 0n;
B = A1 * B + B1 * D;
if (!USE_HALF_EXTENDED) {
A = A1 * A + B1 * C;
}
}
if (USE_HALF_EXTENDED) {
// A*a + B*b = g
A = a0 === 0n ? 0n : (a - B * b0) / a0; // exact division
}
return [A, B, a];
}
function gcd(a, b) {
return LehmersGCD(BigInt(a), BigInt(b));
}
function gcdext(a, b) {
return LehmersGCDExt(BigInt(a), BigInt(b));
}
function invmod(a, m) {
const [A, B, C, D, a1, b1] = halfgcd(m, a, true, false);
if (BigInt(b1) !== 0n) {
const [A1, B1, g] = smallgcdext(a1, b1);
if (BigInt.asUintN(64, g) !== 1n) {
return 0n;
}
const B2 = A1 * BigInt(B) + B1 * BigInt(D);
if (B2 < 0n) {
return B2 + m;
}
return B2;
}
if (BigInt(a1) !== 1n) {
return 0n;
}
const B1 = BigInt(B);
if (B1 < 0n) {
return B1 + m;
}
return B1;
}
function halfgcdWrapper(a, b) {
// reduce numbers as much as possible:
return halfgcd(a, b, true, true, true);
}
export default gcd;
gcd.halfgcd = halfgcdWrapper;//TODO:?
gcd.gcdext = gcdext;//TODO:?
gcd.invmod = invmod;//TODO:?