RosettaCodeData/Task/P-Adic-square-roots/Rust/p-adic-square-roots.rs
2025-06-11 20:16:52 -04:00

381 lines
12 KiB
Rust

use std::cmp::Ordering;
use std::fmt;
#[derive(Clone)]
struct PAdicSquareRoot {
prime: u32,
precision: u32,
digits: Vec<u32>,
order: i32,
}
impl PAdicSquareRoot {
// Create a PAdicSquareRoot number, with p = 'prime', from the given rational 'numerator' / 'denominator'.
fn new(prime: u32, precision: u32, mut numerator: i32, mut denominator: i32) -> Result<Self, String> {
if denominator == 0 {
return Err("Denominator cannot be zero".to_string());
}
let digits_size = precision + 5;
let mut order = 0;
let mut digits = vec![0; digits_size as usize];
// Process rational zero
if numerator == 0 {
return Ok(Self {
prime,
precision,
digits,
order: 1000, // ORDER_MAX from original code
});
}
// Remove multiples of 'prime' and adjust the order of the PAdicSquareRoot number accordingly
while Self::modulo(numerator as i64, prime as i64) == 0 {
numerator /= prime as i32;
order += 1;
}
while Self::modulo(denominator as i64, prime as i64) == 0 {
denominator /= prime as i32;
order -= 1;
}
if (order & 1) != 0 {
return Err(format!("Number does not have a square root in {}-adic", prime));
}
order >>= 1;
let mut result = Self {
prime,
precision,
digits: Vec::with_capacity(digits_size as usize),
order,
};
if prime == 2 {
result.square_root_even_prime(numerator, denominator)?;
} else {
result.square_root_odd_prime(numerator, denominator)?;
}
// Ensure we have the right number of digits
while result.digits.len() < digits_size as usize {
result.digits.push(0);
}
result.digits.truncate(digits_size as usize);
Ok(result)
}
// Create a PAdicSquareRoot directly from a vector of digits
fn from_digits(prime: u32, precision: u32, digits: Vec<u32>, order: i32) -> Self {
Self {
prime,
precision,
digits,
order,
}
}
// Return the additive inverse of this PAdicSquareRoot number
fn negate(&self) -> Self {
if self.digits.is_empty() {
return self.clone();
}
let mut negated = self.digits.clone();
Self::negate_digits(&mut negated, self.prime);
Self::from_digits(self.prime, self.precision, negated, self.order)
}
// Return the product of this PAdicSquareRoot number and another PAdicSquareRoot number
fn multiply(&self, other: &Self) -> Result<Self, String> {
if self.prime != other.prime {
return Err("Cannot multiply p-adic's with different primes".to_string());
}
if self.digits.is_empty() || other.digits.is_empty() {
return Self::new(self.prime, self.precision, 0, 1);
}
let product_digits = Self::multiply_digits(&self.digits, &other.digits, self.prime, (self.precision + 5) as usize);
Ok(Self::from_digits(
self.prime,
self.precision,
product_digits,
self.order + other.order,
))
}
// Return a string representation of this PAdicSquareRoot as a rational number
fn convert_to_rational(&self) -> Result<String, String> {
if self.digits.is_empty() {
return Ok("0 / 1".to_string());
}
// Lagrange lattice basis reduction in two dimensions
let mut series_sum: i64 = self.digits[0] as i64;
let mut maximum_prime: i64 = 1;
for i in 1..self.precision {
maximum_prime *= self.prime as i64;
series_sum += (self.digits[i as usize] as i64) * maximum_prime;
}
let mut one = vec![maximum_prime, series_sum];
let mut two = vec![0, 1];
let mut previous_norm = series_sum * series_sum + 1;
let mut current_norm = previous_norm + 1;
let mut i = 0;
let mut j = 1;
while previous_norm < current_norm {
let numerator = one[i] * one[j] + two[i] * two[j];
let denominator = previous_norm;
current_norm = ((numerator as f64) / (denominator as f64) + 0.5).floor() as i64;
one[i] -= current_norm * one[j];
two[i] -= current_norm * two[j];
current_norm = previous_norm;
previous_norm = one[i] * one[i] + two[i] * two[i];
if previous_norm < current_norm {
std::mem::swap(&mut i, &mut j);
}
}
let mut x = one[j];
let mut y = two[j];
if y < 0 {
y = -y;
x = -x;
}
if (one[i] * y - x * two[i]).abs() != maximum_prime {
return Err("Rational reconstruction failed.".to_string());
}
for _ in self.order..0 {
y *= self.prime as i64;
}
for _ in 0..self.order {
x *= self.prime as i64;
}
Ok(format!("{} / {}", x, y))
}
// Create a 2-adic number which is the square root of the rational 'numerator' / 'denominator'
fn square_root_even_prime(&mut self, numerator: i32, denominator: i32) -> Result<(), String> {
if Self::modulo((numerator as i64) * (denominator as i64), 8) != 1 {
return Err("Number does not have a square root in 2-adic".to_string());
}
// First digit
let mut sum: i64 = 1;
self.digits.push(1);
// Further digits
let digits_size = self.precision + 5;
while self.digits.len() < digits_size as usize {
let factor = (denominator as i64) * sum * sum - (numerator as i64);
let mut valuation = 0;
let mut factor_temp = factor;
while Self::modulo(factor_temp, 2) == 0 {
factor_temp /= 2;
valuation += 1;
}
sum += 2i64.pow(valuation - 1);
while self.digits.len() < (valuation - 1) as usize {
self.digits.push(0);
}
self.digits.push(1);
}
Ok(())
}
// Create a p-adic number, with an odd prime number, p = 'prime',
// which is the p-adic square root of the given rational 'numerator' / 'denominator'
fn square_root_odd_prime(&mut self, numerator: i32, denominator: i32) -> Result<(), String> {
// First digit
let mut first_digit = 0;
for i in 1..self.prime {
if Self::modulo(
(denominator as i64) * (i as i64) * (i as i64) - (numerator as i64),
self.prime as i64,
) == 0
{
first_digit = i;
break;
}
}
if first_digit == 0 {
return Err(format!(
"Number does not have a square root in {}-adic",
self.prime
));
}
self.digits.push(first_digit);
// Further digits
let coefficient = Self::modulo_inverse(
Self::modulo(2 * (denominator as i64) * (first_digit as i64), self.prime as i64) as u32,
self.prime,
);
let mut sum: i64 = first_digit as i64;
let digits_size = self.precision + 5;
for i in 2..digits_size {
let mut next_sum = sum - ((coefficient as i64) * ((denominator as i64) * sum * sum - (numerator as i64)));
next_sum = Self::modulo(next_sum, (self.prime as i64).pow(i));
next_sum -= sum;
sum += next_sum;
let digit = (next_sum / (self.prime as i64).pow(i - 1)) as u32;
self.digits.push(digit);
}
Ok(())
}
// Transform the given vector of digits representing a p-adic number
// into a vector which represents the negation of the p-adic number
fn negate_digits(numbers: &mut Vec<u32>, prime: u32) {
if !numbers.is_empty() {
numbers[0] = Self::modulo(prime as i64 - numbers[0] as i64, prime as i64) as u32;
for i in 1..numbers.len() {
numbers[i] = prime - 1 - numbers[i];
}
}
}
// Return the list obtained by multiplying the digits of the given two lists
fn multiply_digits(one: &[u32], two: &[u32], prime: u32, max_size: usize) -> Vec<u32> {
let mut product = vec![0; one.len() + two.len()];
for b in 0..two.len() {
let mut carry = 0;
for a in 0..one.len() {
product[a + b] += one[a] * two[b] + carry;
carry = product[a + b] / prime;
product[a + b] %= prime;
}
if b + one.len() < product.len() {
product[b + one.len()] = carry;
}
}
// Truncate to max_size
product.truncate(max_size);
product
}
// Return the multiplicative inverse of the given number modulo 'prime'
fn modulo_inverse(number: u32, prime: u32) -> u32 {
let mut inverse: u32 = 1;
while Self::modulo((inverse as i64) * (number as i64), prime as i64) != 1 {
inverse += 1;
}
inverse
}
// Return the given number modulo 'prime' in the range 0..'prime' - 1
fn modulo(number: i64, modulus: i64) -> i64 {
let div = number % modulus;
if div >= 0 {
div
} else {
div + modulus
}
}
// Generate string representation of this PAdicSquareRoot
fn to_string(&self) -> String {
let mut numbers = self.digits.clone();
// Ensure we have the right number of digits
while numbers.len() < (self.precision + 5) as usize {
numbers.push(0);
}
let mut result = String::new();
for i in (0..numbers.len()).rev() {
result.push_str(&numbers[i].to_string());
}
if self.order >= 0 {
for _ in 0..self.order {
result.push('0');
}
result.push_str(".0");
} else {
let insert_pos = result.len() as i32 + self.order;
if insert_pos >= 0 {
result.insert(insert_pos as usize, '.');
} else {
// If we need to insert before the start, pad with zeros
let zeros_needed = -insert_pos;
result = "0.".to_string() + &"0".repeat(zeros_needed as usize) + &result;
}
// Remove trailing zeros
while result.ends_with('0') {
result.pop();
}
}
// Return with ellipsis at the beginning
let start_pos = if result.len() > self.precision as usize + 1 {
result.len() - self.precision as usize - 1
} else {
0
};
format!(" ...{}", &result[start_pos..])
}
}
fn main() {
let tests = vec![
vec![2, 20, 497, 10496],
vec![5, 14, 86, 25],
vec![7, 10, -19, 1],
];
for test in tests {
println!(
"Number: {} / {} in {}-adic",
test[2], test[3], test[0]
);
match PAdicSquareRoot::new(test[0] as u32, test[1] as u32, test[2], test[3]) {
Ok(square_root) => {
println!("The two square roots are:");
println!(" {}", square_root.to_string());
println!(" {}", square_root.negate().to_string());
match square_root.multiply(&square_root) {
Ok(square) => {
println!("The p-adic value is {}", square.to_string());
match square.convert_to_rational() {
Ok(rational) => println!("The rational value is {}", rational),
Err(e) => println!("Error converting to rational: {}", e),
}
},
Err(e) => println!("Error calculating square: {}", e),
}
},
Err(e) => println!("Error: {}", e),
}
println!();
}
}