Skip to content

Instantly share code, notes, and snippets.

@8mist
Last active March 22, 2024 17:48
Show Gist options
  • Save 8mist/affe585ccb7b2969fed83ff0e90d7c14 to your computer and use it in GitHub Desktop.
Save 8mist/affe585ccb7b2969fed83ff0e90d7c14 to your computer and use it in GitHub Desktop.
[1kyu] Codewars - Express number as sum of four squares
const ROBUST_TEST_SET = [
[2047n, [2n]],
[1373653n, [2n, 3n]],
[9080191n, [31n, 73n]],
[25326001n, [2n, 3n, 5n]],
[3215031751n, [2n, 3n, 5n, 7n]],
[4759123141n, [2n, 7n, 61n]],
[1122004669633n, [2n, 13n, 23n, 1662803n]],
[2152302898747n, [2n, 3n, 5n, 7n, 11n]],
[3474749660383n, [2n, 3n, 5n, 7n, 11n, 13n]],
[341550071728321n, [2n, 3n, 5n, 7n, 11n, 13n, 17n]],
[3825123056546413051n, [2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n]],
[
18446744073709551616n,
[2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n],
],
];
const RABIN_EXCEPTIONS = {
5: [2n, 1n, 0n, 0n],
10: [3n, 1n, 0n, 0n],
13: [3n, 2n, 0n, 0n],
34: [5n, 3n, 0n, 0n],
37: [6n, 1n, 0n, 0n],
58: [7n, 3n, 0n, 0n],
61: [6n, 5n, 0n, 0n],
85: [9n, 2n, 0n, 0n],
130: [11n, 3n, 0n, 0n],
214: [14n, 3n, 3n, 0n],
226: [15n, 1n, 0n, 0n],
370: [19n, 3n, 0n, 0n],
526: [21n, 9n, 2n, 0n],
706: [25n, 9n, 0n, 0n],
730: [27n, 1n, 0n, 0n],
829: [27n, 10n, 0n, 0n],
1414: [33n, 18n, 1n, 0n],
1549: [35n, 18n, 0n, 0n],
1906: [41n, 15n, 0n, 0n],
2986: [45n, 31n, 0n, 0n],
7549: [85n, 18n, 0n, 0n],
9634: [97n, 15n, 0n, 0n],
};
function getBitWidth(n) {
let e = 1n;
while (1n << e <= n) {
++e;
}
return e - 1n;
}
function getMin(a, b) {
return a > b ? b : a;
}
function getIntSqrt(n) {
if (n <= 1n) {
return n;
}
let x0 = n;
let x1 = getMin(1n << (getBitWidth(n) / 2n + 1n), n / 2n);
while (x1 < x0) {
x0 = x1;
x1 = (x0 + n / x0) / 2n;
}
return x0;
}
function isPerfectSquare(n) {
const sqrt = getIntSqrt(n);
return sqrt * sqrt === n;
}
function getGCD(a, b) {
return b === 0n ? a : getGCD(b, a % b);
}
function getAbs(a) {
return a > 0 ? a : -a;
}
function getModPow(val, exp, mod) {
let result = 1n,
subPower = val % mod;
while (exp > 0n) {
if ((exp & 1n) === 1n) {
result = (result * subPower) % mod;
}
exp >>= 1n;
subPower = (subPower * subPower) % mod;
}
return result;
}
function doMillerRabinTest(n, a) {
let d = n - 1n;
while (d % 2n === 0n) {
d /= 2n;
}
let x = getModPow(a, d, n);
if (x === 1n || x === n - 1n) {
return true;
}
while (d !== n - 1n) {
x = (x * x) % n;
d *= 2n;
if (x === n - 1n) {
return true;
}
}
return false;
}
class RNG {
constructor() {
this.seed = 1n;
}
next31Bit() {
this.seed = (1103515245n * this.seed + 12345n) % 0x7fffffffn;
return this.seed;
}
nextRange(bound) {
const width = getBitWidth(bound);
let result = 0n;
for (let pos = 0n; pos < width; pos += 31n) {
result = (result << 31n) | this.next31Bit();
}
return result % bound;
}
}
function isPrime(n) {
if (n === 2n) {
return true;
} else if (n <= 1n || n % 2n === 0n) {
return false;
}
let robustTests = ROBUST_TEST_SET.find(([bound]) => n < bound)?.[1];
if (robustTests === undefined) {
const rng = new RNG();
robustTests = Array.from({ length: 15 }, () => rng.next31Bit());
}
return robustTests.every((a) => {
return doMillerRabinTest(n, a);
});
}
function factorizeRecursively(n, result) {
if (n <= 1n) {
return;
} else if (isPrime(n)) {
result.push(n);
return;
} else if (n % 2n === 0n) {
result.push(2n);
factorizeRecursively(n / 2n, result);
return;
}
const rng = new RNG();
let xs,
xt,
c,
factor = n;
const f = (x) => (((x * x) % n) + c) % n;
do {
if (factor === n) {
xs = xt = rng.nextRange(n - 2n) + 2n;
c = rng.nextRange(20n) + 1n;
}
xs = f(xs);
xt = f(f(xt));
factor = getGCD(getAbs(xs - xt), n);
} while (factor === 1n || factor === n);
factorizeRecursively(factor, result);
factorizeRecursively(n / factor, result);
}
function factorize(n) {
const result = [];
factorizeRecursively(n, result);
return result.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
}
function mergeAsTwo(a, b) {
return [getAbs(a[0] * b[0] - a[1] * b[1]), a[0] * b[1] + a[1] * b[0], 0n, 0n];
}
function getRemainders(a, b, bound) {
const boundSqrt = getIntSqrt(bound);
const result = [];
while (b !== 0n) {
const r = a % b;
if (r <= boundSqrt) {
result.push(r);
}
a = b;
b = r;
}
return result;
}
function findSumOfTwo(p) {
if (p === 1n) {
return [1n, 0n, 0n, 0n];
}
if (p === 2n) {
return [1n, 1n, 0n, 0n];
}
let rems = [];
const rng = new RNG();
do {
let q = rng.nextRange(p);
while (getModPow(q, (p - 1n) / 2n, p) !== p - 1n) {
q = rng.nextRange(p);
}
const x = getModPow(q, (p - 1n) / 4n, p);
rems = getRemainders(p, x, p);
} while (rems.length <= 2);
return [rems[0], rems[1], 0n, 0n];
}
function findSumOfThree(n) {
if (RABIN_EXCEPTIONS[n]) {
return RABIN_EXCEPTIONS[n];
} else if (n % 4n === 0n) {
const sub = findSumOfThree(n / 4n);
return [sub[0] * 2n, sub[1] * 2n, sub[2] * 2n, sub[3] * 2n];
} else if (n % 8n === 7n) {
return [0n, 0n, 0n, 0n]; // Exception - Impossible
} else if (n % 8n === 3n) {
let x, p;
const rng = new RNG();
do {
x = rng.nextRange(getIntSqrt(n) + 1n);
p = (n - x * x) / 2n;
} while ((n - x * x) % 2n !== 0n || !(isPrime(p) || p === 1n));
const two = findSumOfTwo(p);
return [x, two[0] + two[1], getAbs(two[0] - two[1]), 0n];
} else if (isPerfectSquare(n)) {
return [getIntSqrt(n), 0n, 0n, 0n];
} else {
let x, p;
const rng = new RNG();
do {
x = rng.nextRange(getIntSqrt(n) + 1n);
p = n - x * x;
} while (!isPrime(p));
const two = findSumOfTwo(p);
return [x, two[0], two[1], 0n];
}
}
function fourSquares(n) {
if (typeof n !== "bigint") {
n = BigInt(n);
}
if (n === 1n) {
return [1n, 0n, 0n, 0n];
} else if (n === 2n) {
return [1n, 1n, 0n, 0n];
} else if (n === 3n) {
return [1n, 1n, 1n, 0n];
}
if (isPerfectSquare(n)) {
return [getIntSqrt(n), 0n, 0n, 0n];
}
if (n % 8n === 7n) {
const sub = findSumOfThree(n - 4n);
return [2n, sub[0], sub[1], sub[2]];
}
return findSumOfThree(n);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment