RosettaCodeData/Task/P-Adic-numbers-basic/JavaScript/p-adic-numbers-basic.js

311 lines
8.4 KiB
JavaScript

// Rational class for rational number representation
class Rational {
constructor(numerator, denominator) {
if (denominator < 0) {
this.numerator = -numerator;
this.denominator = -denominator;
} else {
this.numerator = numerator;
this.denominator = denominator;
}
if (numerator === 0) {
this.denominator = 1;
}
const divisor = this.gcd(Math.abs(this.numerator), this.denominator);
this.numerator = Math.floor(this.numerator / divisor);
this.denominator = Math.floor(this.denominator / divisor);
}
gcd(a, b) {
while (b !== 0) {
const temp = b;
b = a % b;
a = temp;
}
return a;
}
toString() {
return `${this.numerator} / ${this.denominator}`;
}
}
// P_adic class for p-adic number representation
class P_adic {
static PRECISION = 40;
static ORDER_MAX = 1000;
static DIGITS_SIZE = P_adic.PRECISION + 5;
// Create a P_adic number with p = 'prime', from the given rational 'numerator' / 'denominator'
constructor(prime, numerator, denominator, digits = null, order = null) {
this.prime = prime;
// If digits and order are provided, use them directly (for internal use)
if (digits !== null && order !== null) {
this.digits = digits;
this.order = order;
return;
}
if (denominator === 0) {
throw new Error("Denominator cannot be zero");
}
this.order = 0;
// Process rational zero
if (numerator === 0) {
this.digits = Array(P_adic.DIGITS_SIZE).fill(0);
this.order = P_adic.ORDER_MAX;
return;
}
// Remove multiples of 'prime' and adjust the order of the P_adic number accordingly
while (this.moduloPrime(numerator) === 0) {
numerator = Math.floor(numerator / prime);
this.order += 1;
}
while (this.moduloPrime(denominator) === 0) {
denominator = Math.floor(denominator / prime);
this.order -= 1;
}
// Standard calculation of P_adic digits
const inverse = this.moduloInverse(denominator);
this.digits = [];
while (this.digits.length < P_adic.DIGITS_SIZE) {
const digit = this.moduloPrime(numerator * inverse);
this.digits.push(digit);
numerator -= digit * denominator;
if (numerator !== 0) {
// The denominator is not a power of a prime
let count = 0;
while (this.moduloPrime(numerator) === 0) {
numerator = Math.floor(numerator / prime);
count += 1;
}
for (let i = count; i > 1; --i) {
this.digits.push(0);
}
}
}
}
// Return the sum of this P_adic number with the given P_adic number
add(other) {
if (this.prime !== other.prime) {
throw new Error("Cannot add p-adic's with different primes");
}
let this_digits = [...this.digits];
let other_digits = [...other.digits];
let result = [];
// Adjust the digits so that the P_adic points are aligned
for (let i = 0; i < -this.order + other.order; ++i) {
other_digits.unshift(0);
}
for (let i = 0; i < -other.order + this.order; ++i) {
this_digits.unshift(0);
}
// Standard digit by digit addition
let carry = 0;
for (let i = 0; i < Math.min(this_digits.length, other_digits.length); ++i) {
const sum = this_digits[i] + other_digits[i] + carry;
const remainder = sum % this.prime;
carry = (sum >= this.prime) ? 1 : 0;
result.push(remainder);
}
return new P_adic(
this.prime,
null,
null,
result,
this.allZeroDigits(result) ? P_adic.ORDER_MAX : Math.min(this.order, other.order)
);
}
// Return the Rational representation of this P_adic number
convertToRational() {
let numbers = [...this.digits];
// Zero
if (numbers.length === 0 || this.allZeroDigits(numbers)) {
return new Rational(0, 1);
}
// Positive integer
if (this.order >= 0 && this.endsWith(numbers, 0)) {
for (let i = 0; i < this.order; ++i) {
numbers.unshift(0);
}
return new Rational(this.convertToDecimal(numbers), 1);
}
// Negative integer
if (this.order >= 0 && this.endsWith(numbers, this.prime - 1)) {
this.negateDigits(numbers);
for (let i = 0; i < this.order; ++i) {
numbers.unshift(0);
}
return new Rational(-this.convertToDecimal(numbers), 1);
}
// Rational
const copy = new P_adic(this.prime, null, null, [...this.digits], this.order);
let sum = new P_adic(this.prime, null, null, [...this.digits], this.order);
let denominator = 1;
do {
sum = sum.add(copy);
denominator += 1;
} while (!(this.endsWith(sum.digits, 0) || this.endsWith(sum.digits, this.prime - 1)));
const negative = this.endsWith(sum.digits, this.prime - 1);
if (negative) {
this.negateDigits(sum.digits);
}
let numerator = negative ? -this.convertToDecimal(sum.digits) : this.convertToDecimal(sum.digits);
if (this.order > 0) {
numerator *= Math.pow(this.prime, this.order);
}
if (this.order < 0) {
denominator *= Math.pow(this.prime, -this.order);
}
return new Rational(numerator, denominator);
}
// Return a string representation of this P_adic number
toString() {
let numbers = [...this.digits];
this.padWithZeros(numbers);
let result = "";
for (let i = numbers.length - 1; i >= 0; --i) {
result += this.digits[i].toString();
}
if (this.order >= 0) {
for (let i = 0; i < this.order; ++i) {
result += "0";
}
result += ".0";
} else {
result = result.slice(0, result.length + this.order) + "." + result.slice(result.length + this.order);
while (result[result.length - 1] === '0') {
result = result.substring(0, result.length - 1);
}
}
return " ..." + result.substring(result.length - P_adic.PRECISION - 1);
}
// Transform the given array of digits representing a P_adic number
// into an array which represents the negation of the P_adic number
negateDigits(numbers) {
numbers[0] = this.moduloPrime(this.prime - numbers[0]);
for (let i = 1; i < numbers.length; ++i) {
numbers[i] = this.prime - 1 - numbers[i];
}
}
// Return the multiplicative inverse of the given number modulo 'prime'
moduloInverse(number) {
let inverse = 1;
while (this.moduloPrime(inverse * number) !== 1) {
inverse += 1;
}
return inverse;
}
// Return the given number modulo 'prime' in the range 0...'prime' - 1
moduloPrime(number) {
const div = number % this.prime;
return (div >= 0) ? div : div + this.prime;
}
// The given array is padded on the right by zeros up to a maximum length of 'DIGITS_SIZE'
padWithZeros(array) {
while (array.length < P_adic.DIGITS_SIZE) {
array.push(0);
}
}
// Return the given array of base 'prime' integers converted to a decimal integer
convertToDecimal(numbers) {
let decimal = 0;
let multiple = 1;
for (const number of numbers) {
decimal += number * multiple;
multiple *= this.prime;
}
return decimal;
}
// Return whether the given array consists of all zeros
allZeroDigits(numbers) {
for (const number of numbers) {
if (number !== 0) {
return false;
}
}
return true;
}
// Return whether the given array ends with multiple instances of the given number
endsWith(numbers, number) {
for (let i = numbers.length - 1; i >= numbers.length - P_adic.PRECISION / 2; --i) {
if (numbers[i] !== number) {
return false;
}
}
return true;
}
}
// Main function equivalent
function main() {
console.log("3-adic numbers:");
let padic_one = new P_adic(3, -2, 87);
console.log("-2 / 87 => " + padic_one.toString());
let padic_two = new P_adic(3, 4, 97);
console.log("4 / 97 => " + padic_two.toString());
let sum = padic_one.add(padic_two);
console.log("sum => " + sum.toString());
console.log("Rational = " + sum.convertToRational().toString());
console.log();
console.log("7-adic numbers:");
padic_one = new P_adic(7, 5, 8);
console.log("5 / 8 => " + padic_one.toString());
padic_two = new P_adic(7, 353, 30809);
console.log("353 / 30809 => " + padic_two.toString());
sum = padic_one.add(padic_two);
console.log("sum => " + sum.toString());
console.log("Rational = " + sum.convertToRational().toString());
console.log();
}
// Run the main function
main();