use num_bigint::BigInt;
use num_rational::BigRational;
use pnc_engine::Value;
use pnc_engine::persist::DisplayMode;
pub fn format_value(value: &Value, mode: DisplayMode, precision: u8) -> String {
match value {
Value::Int(n) => {
let r = BigRational::from_integer(n.clone());
format_rational(&r, mode, precision, None)
}
Value::Rational(r) => format_rational(r, mode, precision, None),
Value::Unit { number, unit } => {
let r = match number.as_ref() {
Value::Int(n) => BigRational::from_integer(n.clone()),
Value::Rational(r) => r.clone(),
_ => BigRational::from_integer(BigInt::from(0)),
};
let formatted = format_rational(&r, mode, precision, None);
format!("{} {}", formatted, unit)
}
}
}
fn format_rational(
r: &BigRational,
mode: DisplayMode,
precision: u8,
_unit: Option<&str>,
) -> String {
match mode {
DisplayMode::Auto => r.to_string_custom(),
DisplayMode::Fix => format_fix(r, precision),
DisplayMode::Sci => format_sci(r, precision),
DisplayMode::Eng => format_eng(r, precision),
}
}
trait ToStrCustom {
fn to_string_custom(&self) -> String;
}
impl ToStrCustom for BigRational {
fn to_string_custom(&self) -> String {
if self.denom() == &BigInt::from(1) {
self.numer().to_string()
} else {
format!("{}/{}", self.numer(), self.denom())
}
}
}
fn round_half_away(r: &BigRational) -> BigInt {
let (n, d) = (r.numer(), r.denom());
let half_d = d / BigInt::from(2);
let adjusted = if n >= &BigInt::from(0) {
n + half_d
} else {
n - half_d
};
adjusted / d
}
fn format_fix(r: &BigRational, precision: u8) -> String {
if precision == 0 {
let rounded = round_half_away(r);
return rounded.to_string();
}
let scale = BigInt::from(10).pow(precision as u32);
let scaled = r * BigRational::from_integer(scale.clone());
let rounded = round_half_away(&scaled);
let s = rounded.to_string();
if s.len() <= precision as usize {
let padded = format!("{:0>width$}", s, width = precision as usize + 1);
let (int_part, frac_part) = padded.split_at(padded.len() - precision as usize);
format!("{}.{}", int_part, frac_part)
} else {
let (int_part, frac_part) = s.split_at(s.len() - precision as usize);
format!("{}.{}", int_part, frac_part)
}
}
fn format_sci(r: &BigRational, precision: u8) -> String {
format_scientific(r, precision, false)
}
fn format_eng(r: &BigRational, precision: u8) -> String {
format_scientific(r, precision, true)
}
fn format_scientific(r: &BigRational, precision: u8, engineering: bool) -> String {
if r == &BigRational::from_integer(BigInt::from(0)) {
let frac_digits = if precision == 0 {
0
} else {
precision as usize - 1
};
let frac = "0".repeat(frac_digits);
if frac.is_empty() {
return "0e0".to_string();
}
return format!("0.{}e0", frac);
}
let negative = r < &BigRational::from_integer(BigInt::from(0));
let abs_r = if negative { -r.clone() } else { r.clone() };
let (exp, _) = if engineering {
find_eng_exponent(&abs_r)
} else {
find_sci_exponent(&abs_r)
};
let ten_pow_exp = BigInt::from(10).pow(exp.unsigned_abs());
let mantissa = if exp >= 0 {
&abs_r / BigRational::from_integer(ten_pow_exp.clone())
} else {
&abs_r * BigRational::from_integer(ten_pow_exp.clone())
};
let frac_digits = if precision == 0 {
0
} else {
precision as usize - 1
};
let scale = BigInt::from(10).pow(frac_digits as u32);
let scaled_mantissa = &mantissa * BigRational::from_integer(scale.clone());
let rounded_scaled = round_half_away(&scaled_mantissa);
let rounded_mantissa = BigRational::new(rounded_scaled.clone(), scale.clone());
let upper_bound = if engineering {
BigRational::from_integer(BigInt::from(1000))
} else {
BigRational::from_integer(BigInt::from(10))
};
let (final_mantissa, final_exp) = if rounded_mantissa >= upper_bound {
let divisor = if engineering {
BigRational::from_integer(BigInt::from(1000))
} else {
BigRational::from_integer(BigInt::from(10))
};
let new_m = rounded_mantissa / divisor;
let new_e = exp + if engineering { 3 } else { 1 };
(new_m, new_e)
} else {
(rounded_mantissa, exp)
};
let mantissa_str = format_mantissa(&final_mantissa, frac_digits);
let exp_str = format!("e{}", final_exp);
let sign = if negative { "-" } else { "" };
format!("{}{}{}", sign, mantissa_str, exp_str)
}
fn find_sci_exponent(r: &BigRational) -> (i32, ()) {
if r >= &BigRational::from_integer(BigInt::from(1)) {
let mut exp = 0i32;
let mut current = r.clone();
while current >= BigRational::from_integer(BigInt::from(10)) {
current /= BigRational::from_integer(BigInt::from(10));
exp += 1;
}
(exp, ())
} else {
let mut exp = 0i32;
let mut current = r.clone();
while current < BigRational::from_integer(BigInt::from(1))
&& current > BigRational::from_integer(BigInt::from(0))
{
current *= BigRational::from_integer(BigInt::from(10));
exp -= 1;
}
(exp, ())
}
}
fn find_eng_exponent(r: &BigRational) -> (i32, ()) {
let (sci_exp, _) = find_sci_exponent(r);
let remainder = sci_exp.rem_euclid(3);
let eng_exp = sci_exp - remainder;
(eng_exp, ())
}
fn format_mantissa(m: &BigRational, frac_digits: usize) -> String {
if frac_digits == 0 {
let rounded = round_half_away(m);
return rounded.to_string();
}
let scale = BigInt::from(10).pow(frac_digits as u32);
let scaled = m * BigRational::from_integer(scale.clone());
let rounded = round_half_away(&scaled);
let s = rounded.to_string();
if s.len() <= frac_digits {
let padded = format!("{:0>width$}", s, width = frac_digits + 1);
let int_part = &padded[..1];
let frac_part = &padded[1..];
format!("{}.{}", int_part, frac_part)
} else {
let int_part = &s[..s.len() - frac_digits];
let frac_part = &s[s.len() - frac_digits..];
if int_part.len() > 1 {
format!("{}.{}", &int_part[..1], &int_part[1..])
} else {
format!("{}.{}", int_part, frac_part)
}
}
}
pub fn precision_for(settings: &pnc_engine::persist::Settings, mode: DisplayMode) -> u8 {
match mode {
DisplayMode::Auto => 0,
DisplayMode::Fix => settings.fix_decimals,
DisplayMode::Sci => settings.sci_digits,
DisplayMode::Eng => settings.eng_digits,
}
}