use crate::error::{Result, SankhyaError};
#[must_use]
#[inline]
pub fn vedic_multiply(a: u64, b: u64) -> u64 {
a.wrapping_mul(b)
}
#[must_use = "returns the multiplication steps or an error"]
pub fn vedic_multiply_nikhilam(a: u64, b: u64) -> Result<(u64, u64, u64, u64, u64)> {
let max_val = a.max(b);
let base = next_power_of_10(max_val);
if a > base || b > base {
return Err(SankhyaError::ComputationError(
"values exceed the Nikhilam base".into(),
));
}
let da = base - a; let db = base - b;
let cross = a.checked_sub(db).ok_or_else(|| {
SankhyaError::OverflowError("Nikhilam cross-subtraction underflow".into())
})?;
let product = cross
.checked_mul(base)
.and_then(|cb| cb.checked_add(da.checked_mul(db)?))
.ok_or_else(|| SankhyaError::OverflowError("Nikhilam multiplication overflow".into()))?;
Ok((base, da, db, cross, product))
}
fn next_power_of_10(n: u64) -> u64 {
let mut base = 10;
while base < n {
base = base.saturating_mul(10);
}
base
}
#[must_use]
#[inline]
pub fn sulba_diagonal(a: f64, b: f64) -> f64 {
(a * a + b * b).sqrt()
}
#[must_use]
pub fn sulba_sqrt2() -> f64 {
1.0 + 1.0 / 3.0 + 1.0 / (3.0 * 4.0) - 1.0 / (3.0 * 4.0 * 34.0)
}
#[must_use]
pub fn katapayadi_encode(n: u64) -> String {
let consonants = [
"nya", "ka", "kha", "ga", "gha", "nga", "ca", "cha", "ja", "jha",
];
if n == 0 {
return consonants[0].to_string();
}
let mut result = String::new();
let mut remaining = n;
while remaining > 0 {
let digit = (remaining % 10) as usize;
remaining /= 10;
if !result.is_empty() {
result.push('-');
}
result.push_str(consonants[digit]);
}
result
}
#[must_use = "returns the decoded number or an error"]
pub fn katapayadi_decode(s: &str) -> Result<u64> {
let consonants = [
("nya", 0u64),
("ka", 1),
("kha", 2),
("ga", 3),
("gha", 4),
("nga", 5),
("ca", 6),
("cha", 7),
("ja", 8),
("jha", 9),
];
let parts: Vec<&str> = s.split('-').collect();
let mut result: u64 = 0;
let mut place: u64 = 1;
for part in &parts {
let digit = consonants
.iter()
.find(|(name, _)| name == part)
.map(|(_, d)| *d)
.ok_or_else(|| {
SankhyaError::ComputationError(format!("unrecognized Katapayadi syllable: {part}"))
})?;
result =
result
.checked_add(digit.checked_mul(place).ok_or_else(|| {
SankhyaError::OverflowError("Katapayadi decode overflow".into())
})?)
.ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;
place = place
.checked_mul(10)
.ok_or_else(|| SankhyaError::OverflowError("Katapayadi decode overflow".into()))?;
}
Ok(result)
}
#[must_use = "returns the triangle rows or an error"]
pub fn meru_prastara(rows: usize) -> Result<Vec<Vec<u64>>> {
tracing::debug!(rows, "generating Meru Prastara (Pingala's triangle)");
if rows == 0 {
return Ok(Vec::new());
}
let mut triangle = Vec::with_capacity(rows);
triangle.push(vec![1u64]);
for i in 1..rows {
let prev = &triangle[i - 1];
let mut row = Vec::with_capacity(i + 1);
row.push(1);
for j in 1..i {
let val = prev[j - 1].checked_add(prev[j]).ok_or_else(|| {
SankhyaError::OverflowError(format!(
"Meru Prastara overflow at row {i}, position {j}"
))
})?;
row.push(val);
}
row.push(1);
triangle.push(row);
}
Ok(triangle)
}
#[cfg(feature = "varna")]
#[must_use]
pub fn katapayadi_encode_devanagari(n: u64) -> String {
let table = varna::script::transliteration::devanagari_iast();
let reverse = table.reverse_map();
let iast_syllables = [
"ña", "ka", "kha", "ga", "gha", "ṅa", "ca", "cha", "ja", "jha",
];
if n == 0 {
return reverse.get("ña").copied().unwrap_or("ञ").to_string();
}
let mut result = String::new();
let mut remaining = n;
while remaining > 0 {
let digit = (remaining % 10) as usize;
remaining /= 10;
if !result.is_empty() {
result.push(' ');
}
let syllable = iast_syllables[digit];
result.push_str(reverse.get(syllable).copied().unwrap_or("?"));
}
result
}
#[cfg(feature = "varna")]
#[must_use]
pub fn to_devanagari_digits(n: u64) -> String {
let system = varna::script::numerals::devanagari_digits();
if n == 0 {
return system.char_for(0).unwrap_or("०").to_string();
}
let mut digits = Vec::new();
let mut remaining = n;
while remaining > 0 {
let d = (remaining % 10) as u32;
digits.push(system.char_for(d).unwrap_or("?").to_string());
remaining /= 10;
}
digits.reverse();
digits.join("")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vedic_multiply_basic() {
assert_eq!(vedic_multiply(97, 96), 9312);
assert_eq!(vedic_multiply(12, 13), 156);
}
#[test]
fn vedic_nikhilam_near_100() {
let (base, da, db, cross, product) = vedic_multiply_nikhilam(97, 96).unwrap();
assert_eq!(base, 100);
assert_eq!(da, 3);
assert_eq!(db, 4);
assert_eq!(cross, 93);
assert_eq!(product, 9312);
}
#[test]
fn sulba_diagonal_3_4_5() {
let d = sulba_diagonal(3.0, 4.0);
assert!((d - 5.0).abs() < 1e-15);
}
#[test]
fn sulba_sqrt2_accuracy() {
let approx = sulba_sqrt2();
assert!((approx - 577.0 / 408.0).abs() < 1e-15);
assert!((approx - std::f64::consts::SQRT_2).abs() < 1e-5);
}
#[test]
fn katapayadi_roundtrip() {
for n in [0, 1, 42, 123, 9876] {
let encoded = katapayadi_encode(n);
let decoded = katapayadi_decode(&encoded).unwrap();
assert_eq!(decoded, n, "roundtrip failed for {n}");
}
}
#[test]
fn meru_prastara_5_rows() {
let triangle = meru_prastara(5).unwrap();
assert_eq!(triangle.len(), 5);
assert_eq!(triangle[0], vec![1]);
assert_eq!(triangle[1], vec![1, 1]);
assert_eq!(triangle[2], vec![1, 2, 1]);
assert_eq!(triangle[3], vec![1, 3, 3, 1]);
assert_eq!(triangle[4], vec![1, 4, 6, 4, 1]);
}
#[cfg(feature = "varna")]
mod devanagari_tests {
use super::*;
#[test]
fn devanagari_digits_basic() {
assert_eq!(to_devanagari_digits(0), "०");
assert_eq!(to_devanagari_digits(123), "१२३");
assert_eq!(to_devanagari_digits(9876), "९८७६");
}
#[test]
fn katapayadi_devanagari_zero() {
let s = katapayadi_encode_devanagari(0);
assert_eq!(s, "ञ");
}
#[test]
fn katapayadi_devanagari_single() {
let s = katapayadi_encode_devanagari(1);
assert_eq!(s, "क");
}
#[test]
fn katapayadi_devanagari_multi() {
let s = katapayadi_encode_devanagari(21);
assert_eq!(s, "क ख");
}
}
}