RosettaCodeData/Task/P-Adic-square-roots/JavaScript/p-adic-square-roots.js

361 lines
9.5 KiB
JavaScript

// PAdicSquareRoot represents a p-adic square root number
class PAdicSquareRoot {
constructor(prime, precision, digits = [], order = 0) {
this.prime = prime;
this.precision = precision;
this.digits = digits;
this.order = order;
}
// Clone returns a copy of the PAdicSquareRoot
clone() {
return new PAdicSquareRoot(
this.prime,
this.precision,
[...this.digits],
this.order
);
}
// Negate returns the additive inverse of this p-adic square root number
negate() {
if (this.digits.length === 0) {
return this.clone();
}
const result = this.clone();
negateDigits(result.digits, this.prime);
return result;
}
// Multiply returns the product of this p-adic square root number and another
multiply(other) {
if (this.prime !== other.prime) {
throw new Error("Cannot multiply p-adic's with different primes");
}
if (this.digits.length === 0 || other.digits.length === 0) {
return createPAdicSquareRoot(this.prime, this.precision, 0, 1);
}
const digitsSize = this.precision + 5;
const product = multiplyDigits(this.digits, other.digits, this.prime, digitsSize);
return new PAdicSquareRoot(
this.prime,
this.precision,
product,
this.order + other.order
);
}
// ConvertToRational returns a string representation of this p-adic square root as a rational number
convertToRational() {
if (this.digits.length === 0) {
return "0 / 1";
}
// Lagrange lattice basis reduction in two dimensions
let seriesSum = this.digits[0];
let maximumPrime = 1n;
for (let i = 1; i < this.precision; i++) {
maximumPrime *= BigInt(this.prime);
seriesSum += this.digits[i] * Number(maximumPrime);
}
const one = [Number(maximumPrime), seriesSum];
const two = [0, 1];
let previousNorm = seriesSum * seriesSum + 1;
let currentNorm = previousNorm + 1;
let i = 0;
let j = 1;
while (previousNorm < currentNorm) {
const numerator = one[i] * one[j] + two[i] * two[j];
const denominator = previousNorm;
currentNorm = Math.floor(numerator / denominator + 0.5);
one[i] -= currentNorm * one[j];
two[i] -= currentNorm * two[j];
currentNorm = previousNorm;
previousNorm = one[i] * one[i] + two[i] * two[i];
if (previousNorm < currentNorm) {
[one[i], one[j]] = [one[j], one[i]];
[two[i], two[j]] = [two[j], two[i]];
}
}
let x = one[j];
let y = two[j];
if (y < 0) {
y = -y;
x = -x;
}
if (Math.abs(one[i] * y - x * two[i]) !== Number(maximumPrime)) {
throw new Error("Rational reconstruction failed");
}
for (let k = this.order; k < 0; k++) {
y *= this.prime;
}
for (let k = 0; k < this.order; k++) {
x *= this.prime;
}
return `${x} / ${y}`;
}
// String returns a string representation of this p-adic square root
toString() {
const digits = [...this.digits];
// Pad with zeros if needed
while (digits.length < this.precision + 5) {
digits.push(0);
}
let result = "";
for (let i = digits.length - 1; i >= 0; i--) {
result += digits[i].toString();
}
let resultStr = result;
if (this.order >= 0) {
for (let i = 0; i < this.order; i++) {
resultStr += "0";
}
resultStr += ".0";
} else {
const insertPos = resultStr.length + this.order;
if (insertPos >= 0) {
resultStr = resultStr.substring(0, insertPos) + "." + resultStr.substring(insertPos);
} else {
// If we need to insert before the start, pad with zeros
const zerosNeeded = -insertPos;
resultStr = "0." + "0".repeat(zerosNeeded) + resultStr;
}
// Remove trailing zeros
while (resultStr.endsWith("0")) {
resultStr = resultStr.substring(0, resultStr.length - 1);
}
}
// Return with ellipsis at the beginning
let startPos = 0;
if (resultStr.length > this.precision + 1) {
startPos = resultStr.length - this.precision - 1;
}
return " ..." + resultStr.substring(startPos);
}
// squareRootEvenPrime creates a 2-adic number which is the square root of the rational numerator/denominator
squareRootEvenPrime(numerator, denominator) {
if (modulo(numerator * denominator, 8) !== 1) {
throw new Error("Number does not have a square root in 2-adic");
}
// First digit
let sum = 1;
this.digits.push(1);
// Further digits
const digitsSize = this.precision + 5;
while (this.digits.length < digitsSize) {
const factor = denominator * sum * sum - numerator;
let valuation = 0;
let factorTemp = factor;
while (modulo(factorTemp, 2) === 0) {
factorTemp = Math.floor(factorTemp / 2);
valuation++;
}
sum += Math.pow(2, valuation - 1);
while (this.digits.length < valuation - 1) {
this.digits.push(0);
}
this.digits.push(1);
}
}
// squareRootOddPrime creates a p-adic number, with an odd prime number, which is the p-adic square root
squareRootOddPrime(numerator, denominator) {
// First digit
let firstDigit = 0;
for (let i = 1; i < this.prime && firstDigit === 0; i++) {
if (modulo(denominator * i * i - numerator, this.prime) === 0) {
firstDigit = i;
}
}
if (firstDigit === 0) {
throw new Error(`Number does not have a square root in ${this.prime}-adic`);
}
this.digits.push(firstDigit);
// Further digits
const coefficient = moduloInverse(modulo(2 * denominator * firstDigit, this.prime), this.prime);
let sum = firstDigit;
const digitsSize = this.precision + 5;
for (let i = 2; i < digitsSize; i++) {
let nextSum = sum - coefficient * (denominator * sum * sum - numerator);
nextSum = modulo(nextSum, Math.pow(this.prime, i));
nextSum -= sum;
sum += nextSum;
const digit = Math.floor(nextSum / Math.pow(this.prime, i - 1));
this.digits.push(digit);
}
}
}
// createPAdicSquareRoot creates a p-adic square root number from a rational number
function createPAdicSquareRoot(prime, precision, numerator, denominator) {
if (denominator === 0) {
throw new Error("Denominator cannot be zero");
}
const digitsSize = precision + 5;
let order = 0;
// Process rational zero
if (numerator === 0) {
return new PAdicSquareRoot(
prime,
precision,
Array(digitsSize).fill(0),
1000 // orderMax
);
}
// Remove multiples of 'prime' and adjust the order accordingly
while (modulo(numerator, prime) === 0) {
numerator = Math.floor(numerator / prime);
order++;
}
while (modulo(denominator, prime) === 0) {
denominator = Math.floor(denominator / prime);
order--;
}
if ((order & 1) !== 0) {
throw new Error(`Number does not have a square root in ${prime}-adic`);
}
order >>= 1;
const result = new PAdicSquareRoot(
prime,
precision,
[],
order
);
if (prime === 2) {
result.squareRootEvenPrime(numerator, denominator);
} else {
result.squareRootOddPrime(numerator, denominator);
}
// Ensure we have the right number of digits
while (result.digits.length < digitsSize) {
result.digits.push(0);
}
if (result.digits.length > digitsSize) {
result.digits = result.digits.slice(0, digitsSize);
}
return result;
}
// negateDigits transforms the given vector of digits representing a p-adic number
// into a vector which represents the negation of the p-adic number
function negateDigits(numbers, prime) {
if (numbers.length > 0) {
numbers[0] = modulo(prime - numbers[0], prime);
for (let i = 1; i < numbers.length; i++) {
numbers[i] = prime - 1 - numbers[i];
}
}
}
// multiplyDigits returns the list obtained by multiplying the digits of the given two lists
function multiplyDigits(one, two, prime, maxSize) {
const product = Array(one.length + two.length).fill(0);
for (let b = 0; b < two.length; b++) {
let carry = 0;
for (let a = 0; a < one.length; a++) {
product[a + b] += one[a] * two[b] + carry;
carry = Math.floor(product[a + b] / prime);
product[a + b] %= prime;
}
if (b + one.length < product.length) {
product[b + one.length] = carry;
}
}
// Truncate to maxSize
if (product.length > maxSize) {
return product.slice(0, maxSize);
}
return product;
}
// moduloInverse returns the multiplicative inverse of the given number modulo 'prime'
function moduloInverse(number, prime) {
let inverse = 1;
while (modulo(inverse * number, prime) !== 1) {
inverse++;
}
return inverse;
}
// modulo returns the given number modulo 'prime' in the range 0..'prime' - 1
function modulo(number, modulus) {
const div = number % modulus;
return div >= 0 ? div : div + modulus;
}
// Main function
function main() {
const tests = [
[2, 20, 497, 10496],
[5, 14, 86, 25]
// ,[7, 10, -19, 1]
];
for (const test of tests) {
console.log(`Number: ${test[2]} / ${test[3]} in ${test[0]}-adic`);
try {
const squareRoot = createPAdicSquareRoot(test[0], test[1], test[2], test[3]);
console.log("The two square roots are:");
console.log(` ${squareRoot.toString()}`);
console.log(` ${squareRoot.negate().toString()}`);
const square = squareRoot.multiply(squareRoot);
console.log(`The p-adic value is ${square.toString()}`);
const rational = square.convertToRational();
console.log(`The rational value is ${rational}`);
} catch (error) {
console.log(`Error: ${error.message}`);
}
console.log();
}
}
// Run the main function
main();