use super::types::{Sign, DECIMAL_CHAR, GROUP_DELIMITER_CHAR};
use crate::FormatError;
use std::cmp::{max, min};
#[allow(dead_code)]
pub(crate) fn get_significant_digits(input: &str) -> usize {
let contains_dot = input.contains('.');
let mut dot_counted = false;
let mut insignificant = 0;
for char in input.chars() {
match char {
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => break,
'.' => {
insignificant += 1;
dot_counted = true;
}
_ => insignificant += 1,
}
}
if !contains_dot {
for char in input.chars().rev() {
match char {
'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => break,
_ => insignificant += 1,
}
}
}
input.len() - insignificant - (contains_dot && !dot_counted) as usize
}
pub(crate) fn decompose_to_coefficient_and_exponent(
value: f64,
significant_digits: Option<usize>,
) -> Result<(String, isize), FormatError> {
let formatted_value = if let Some(significant_digits) = significant_digits {
let precision = if significant_digits == 0 {
0
} else {
significant_digits - 1
};
format!("{:.1$e}", value, precision)
} else {
format!("{:e}", value)
};
let exp_tokens: Vec<&str> = formatted_value.split('e').collect::<Vec<&str>>();
let exponent = exp_tokens[1]
.parse()
.map_err(|_| FormatError::CouldNotDecomposeCoefficientExponent)?;
if exp_tokens[0].len() == 1 {
Ok((exp_tokens[0].to_owned(), exponent))
} else {
let dot_idx = exp_tokens[0]
.chars()
.position(|c| c == DECIMAL_CHAR)
.ok_or(FormatError::CouldNotDecomposeCoefficientExponent)?;
let coefficient = format!(
"{}{}",
&exp_tokens[0][..dot_idx],
&exp_tokens[0][dot_idx + 1..]
);
Ok((coefficient, exponent))
}
}
pub(crate) fn format_si_prefix(
value: f64,
precision: Option<usize>,
) -> Result<(String, isize), FormatError> {
let (coefficient, exponent) = decompose_to_coefficient_and_exponent(value, precision)?;
let prefix_exponent = max(-8, min(8, (exponent as f32 / 3_f32).floor() as isize));
let i: isize = exponent - prefix_exponent * 3 + 1;
let n: isize = coefficient.len() as isize;
if i == n {
Ok((coefficient, prefix_exponent))
} else if i > n {
let coefficient = format!("{}{}", coefficient, "0".repeat((i - n) as usize),);
Ok((coefficient, prefix_exponent))
} else if i > 0 {
let coefficient = format!(
"{}{}{}",
&coefficient[..i as usize],
DECIMAL_CHAR,
&coefficient[i as usize..]
);
Ok((coefficient, prefix_exponent))
} else {
let inner_precision = precision
.map(|p| (p as i32 - i.abs() as i32 - 1) as usize)
.ok_or(FormatError::CouldNotDecomposeCoefficientExponent)?;
let coefficient = decompose_to_coefficient_and_exponent(
value,
precision.and(Some(max(0, inner_precision))),
)?
.0;
let coefficient = format!(
"0{}{}{}",
DECIMAL_CHAR,
"0".repeat(i.unsigned_abs()),
coefficient
);
Ok((coefficient, prefix_exponent))
}
}
pub(crate) fn group_value(value: &str, width: usize) -> String {
let mut reversed_chars: Vec<&[char]> = Vec::new();
let input_chars: Vec<char> = value.chars().rev().collect();
let separator: [char; 1] = [GROUP_DELIMITER_CHAR];
for group in input_chars.chunks(3) {
reversed_chars.push(group);
reversed_chars.push(&separator);
}
reversed_chars.pop();
let grouped: Vec<&char> = reversed_chars.into_iter().flatten().collect();
if width > 0 && grouped.len() > width {
let to_skip = if grouped[width - 1] == &separator[0] {
grouped.len() - width - 1
} else {
grouped.len() - width
};
grouped.into_iter().rev().skip(to_skip).collect::<String>()
} else {
grouped.into_iter().rev().collect::<String>()
}
}
pub(crate) fn get_formatted_exp_value(
format_type: &str,
value: f64,
precision: usize,
include_decimal_point: bool,
) -> String {
let formatted = format!("{:.1$e}", value, precision);
let tokens = formatted.split('e').collect::<Vec<&str>>();
let exp_suffix = if &tokens[1][0..1] == "-" {
if tokens[1].len() == 2 {
format!("-0{}", &tokens[1][1..])
} else {
tokens[1].to_owned()
}
} else {
format!("+{:0>2}", &tokens[1])
};
let possible_decimal = if include_decimal_point && precision == 0 {
format_args!("{}", DECIMAL_CHAR).to_string()
} else {
"".to_owned()
};
format!(
"{}{}{}{}",
&tokens[0], possible_decimal, format_type, exp_suffix
)
}
pub(crate) fn get_sign_prefix(is_negative: bool, format_sign: &Sign) -> &'static str {
if is_negative {
"-"
} else if let Sign::Always = format_sign {
"+"
} else if let Sign::SpaceOrDash = format_sign {
" "
} else {
""
}
}