Files
bqst/bqst-core/src/api.rs
2025-12-21 15:38:31 +01:00

55 lines
1.4 KiB
Rust

use std::fmt::Debug;
use dashu::rational::RBig;
use crate::{PrecDec, prefixes, qty::Qty, unit};
macro_rules! _t {
($e:expr) => {
$e.map_err(|e| Box::new(e) as Box<dyn Debug>)
};
}
fn parse_decimal<const B: u32>(s: &str) -> Result<RBig, dashu::base::ParseError> {
if let Some((int_part, frac_part)) = s.split_once('.') {
let denominator = B.pow(frac_part.len() as u32);
let numerator = RBig::from_str_radix(&format!("{}{}", int_part, frac_part), B)?;
Ok(numerator / RBig::from(denominator))
} else {
Ok(RBig::from_str_radix(s, B)?)
}
}
pub fn qty(q: &str) -> Result<Qty, Box<dyn Debug>> {
let split_idx = q
.char_indices()
.rev()
.find(|&(_, c)| c.is_ascii_digit())
.map(|(i, _)| i + 1) // found digit and digits are 1 byte so no worries byt ugly
.unwrap_or(0);
let (amt, unt) = q.split_at(split_idx);
let mut amt: PrecDec = if amt.is_empty() {
PrecDec::from(1)
} else {
_t!(parse_decimal::<10>(amt))?
};
let unt = if let Some((_, rem, exp)) = prefixes::extract_prefix_and_rem(unt.trim()) {
let e = PrecDec::from(10isize.pow(exp.unsigned_abs().into()));
if exp < 0 {
amt /= e;
} else {
amt *= e;
}
rem
} else {
unt
};
let unt = _t!(unit::Unit::try_from(unt.trim()))?;
Ok(Qty { n: amt, u: unt })
}