pub mod cisbp;
pub mod fasta;
pub mod homer;
pub mod jaspar;
pub mod matrix;
pub mod meme;
pub mod transfac;
pub mod uniprobe;
use crate::error::{Error, Result};
pub(crate) fn content_lines(text: &str) -> Vec<&str> {
text.lines().filter(|l| !l.trim().is_empty()).collect()
}
pub(crate) fn parse_num(s: &str) -> Option<f64> {
let s = s.trim();
if s == "NA" {
return None;
}
s.parse::<f64>().ok().filter(|v| !v.is_nan())
}
pub(crate) fn num(s: &str, what: &str) -> Result<f64> {
parse_num(s).ok_or_else(|| Error::Argument(format!("could not read {what} '{s}' as a number")))
}
pub(crate) fn fmt_err<T>(msg: impl Into<String>) -> Result<T> {
Err(Error::Argument(msg.into()))
}
pub(crate) fn format_r_number(x: f64) -> String {
let target = format!("{x:.14e}").parse().unwrap_or(x);
format_significant(x, 15, target)
}
pub(crate) fn format_shortest(x: f64) -> String {
format_significant(x, 17, x)
}
fn format_significant(x: f64, max_digits: usize, target: f64) -> String {
if x.is_nan() {
return "NaN".into();
}
if x.is_infinite() {
return if x > 0.0 { "Inf".into() } else { "-Inf".into() };
}
if x == 0.0 {
return "0".into();
}
let mut sci = String::new();
for d in 1..=max_digits {
sci = format!("{:.*e}", d - 1, x);
if sci.parse::<f64>().unwrap() == target {
break;
}
}
let (mant, exp) = sci.split_once('e').unwrap();
let exp: i32 = exp.parse().unwrap();
let neg = mant.starts_with('-');
let digits: String = mant.chars().filter(|c| c.is_ascii_digit()).collect();
let sign = if neg { "-" } else { "" };
let sci_str = format!(
"{sign}{}{}e{}{:02}",
&digits[..1],
if digits.len() > 1 {
format!(".{}", &digits[1..])
} else {
String::new()
},
if exp < 0 { '-' } else { '+' },
exp.abs()
);
let decimals = if exp >= 0 {
digits.len().saturating_sub(exp as usize + 1)
} else {
(-exp - 1) as usize + digits.len()
};
let fixed_str = format!("{x:.decimals$}");
if fixed_str.len() <= sci_str.len() {
fixed_str
} else {
sci_str
}
}
pub(crate) fn format_fixed(x: f64, digits: usize) -> String {
if x.is_nan() {
"NaN".into()
} else if x.is_infinite() {
if x > 0.0 { "Inf".into() } else { "-Inf".into() }
} else {
format!("{x:.digits$}")
}
}
pub(crate) fn join_lines(lines: &[String]) -> String {
let mut out = String::with_capacity(lines.iter().map(|l| l.len() + 1).sum());
for l in lines {
out.push_str(l);
out.push('\n');
}
out
}
#[cfg(test)]
mod tests {
use super::format_r_number as f;
#[test]
fn r_number_formatting() {
assert_eq!(f(30.0), "30");
assert_eq!(f(0.5), "0.5");
assert_eq!(f(1.0 / 3.0), "0.333333333333333");
assert_eq!(f(1e5), "1e+05");
assert_eq!(f(123456.0), "123456");
assert_eq!(f(1e-4), "1e-04");
assert_eq!(f(0.001), "0.001");
assert_eq!(f(0.00012), "0.00012");
assert_eq!(f(-2.5e-12), "-2.5e-12");
assert_eq!(f(4.70742403722102), "4.70742403722102");
assert_eq!(f(1e15), "1e+15");
assert_eq!(f(1.5e300), "1.5e+300");
assert_eq!(f(f64::NEG_INFINITY), "-Inf");
assert_eq!(f(100.0), "100");
assert_eq!(f(1234567890123456.0), "1234567890123456");
assert_eq!(f(0.1 + 0.2), "0.3");
assert_eq!(f(2.0 / 3.0 * 1e-7), "6.66666666666667e-08");
}
#[test]
fn shortest_formatting_round_trips() {
use super::format_shortest as s;
assert_eq!(s(0.1), "0.1");
assert_eq!(s(1.0 / 3.0), "0.3333333333333333");
assert_eq!(s(1e-20), "1e-20");
assert_eq!(s(25.0), "25");
for x in [
1.0 / 3.0,
0.17647058823529413,
2.5e-300,
123456.789,
-7.0 / 9.0,
] {
assert_eq!(s(x).parse::<f64>().unwrap(), x);
}
}
}