165 lines
4.3 KiB
Rust
165 lines
4.3 KiB
Rust
use crate::{
|
|
formatter::{self, format::Formatted},
|
|
locale::get_locale,
|
|
types::NumFmt,
|
|
};
|
|
|
|
const DEFAULT_NUM_FMTS: &[&str] = &[
|
|
"general",
|
|
"0",
|
|
"0.00",
|
|
"#,## 0",
|
|
"#,## 0.00",
|
|
"$#,## 0; \\ - $#,## 0",
|
|
"$#,## 0; [Red] \\ - $#,## 0",
|
|
"$#,## 0.00; \\ - $#,## 0.00",
|
|
"$#,## 0.00; [Red] \\ - $#,## 0.00",
|
|
"0%",
|
|
"0.00%",
|
|
"0.00E + 00",
|
|
"#?/?",
|
|
"#?? / ??",
|
|
"mm-dd-yy",
|
|
"d-mmm-yy",
|
|
"d-mmm",
|
|
"mmm-yy",
|
|
"h:mm AM / PM",
|
|
"h:mm:ss AM / PM",
|
|
"h:mm",
|
|
"h:mm:ss",
|
|
"m / d / yy h:mm",
|
|
"#,## 0;()#,## 0)",
|
|
"#,## 0; [Red]()#,## 0)",
|
|
"#,## 0.00;()#,## 0.00)",
|
|
"#,## 0.00; [Red]()#,## 0.00)",
|
|
"_()$”*#,## 0.00 _); _()$”* \\()#,## 0.00\\); _()$”* - ?? _); _()@_)",
|
|
"mm:ss",
|
|
"[h]:mm:ss",
|
|
"mmss .0",
|
|
"## 0.0E + 0",
|
|
"@",
|
|
"[$ -404] e / m / d ",
|
|
"m / d / yy",
|
|
"[$ -404] e / m / d",
|
|
"[$ -404] e / / d",
|
|
"[$ -404] e / m / d",
|
|
"t0",
|
|
"t0.00",
|
|
"t#,## 0",
|
|
"t#,## 0.00",
|
|
"t0%",
|
|
"t0.00 %",
|
|
"t#?/?",
|
|
];
|
|
|
|
pub fn get_default_num_fmt_id(num_fmt: &str) -> Option<i32> {
|
|
for (index, default_num_fmt) in DEFAULT_NUM_FMTS.iter().enumerate() {
|
|
if default_num_fmt == &num_fmt {
|
|
return Some(index as i32);
|
|
};
|
|
}
|
|
None
|
|
}
|
|
|
|
pub fn get_num_fmt(num_fmt_id: i32, num_fmts: &[NumFmt]) -> String {
|
|
// Check if it defined
|
|
for num_fmt in num_fmts {
|
|
if num_fmt.num_fmt_id == num_fmt_id {
|
|
return num_fmt.format_code.clone();
|
|
}
|
|
}
|
|
// Return one of the default ones
|
|
if num_fmt_id < DEFAULT_NUM_FMTS.len() as i32 {
|
|
return DEFAULT_NUM_FMTS[num_fmt_id as usize].to_string();
|
|
}
|
|
// Return general
|
|
DEFAULT_NUM_FMTS[0].to_string()
|
|
}
|
|
|
|
pub fn get_new_num_fmt_index(num_fmts: &[NumFmt]) -> i32 {
|
|
let mut index = DEFAULT_NUM_FMTS.len() as i32;
|
|
let mut found = true;
|
|
while found {
|
|
found = false;
|
|
for num_fmt in num_fmts {
|
|
if num_fmt.num_fmt_id == index {
|
|
found = true;
|
|
index += 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
index
|
|
}
|
|
|
|
pub fn to_precision(value: f64, precision: usize) -> f64 {
|
|
if value.is_infinite() || value.is_nan() {
|
|
return value;
|
|
}
|
|
to_precision_str(value, precision)
|
|
.parse::<f64>()
|
|
.unwrap_or({
|
|
// TODO: do this in a way that does not require a possible error
|
|
0.0
|
|
})
|
|
}
|
|
|
|
/// It rounds a `f64` with `p` significant figures:
|
|
/// ```
|
|
/// use ironcalc_base::number_format;
|
|
/// assert_eq!(number_format::to_precision(0.1+0.2, 15), 0.3);
|
|
/// assert_eq!(number_format::to_excel_precision_str(0.1+0.2), "0.3");
|
|
/// ```
|
|
/// This intends to be equivalent to the js: `${parseFloat(value.toPrecision(precision)})`
|
|
/// See ([ecma](https://tc39.es/ecma262/#sec-number.prototype.toprecision)).
|
|
pub fn to_excel_precision_str(value: f64) -> String {
|
|
to_precision_str(value, 15)
|
|
}
|
|
|
|
pub fn to_excel_precision(value: f64, precision: usize) -> f64 {
|
|
if !value.is_finite() {
|
|
return value;
|
|
}
|
|
|
|
let s = format!("{:.*e}", precision.saturating_sub(1), value);
|
|
s.parse::<f64>().unwrap_or(value)
|
|
}
|
|
|
|
pub fn to_precision_str(value: f64, precision: usize) -> String {
|
|
if !value.is_finite() {
|
|
if value.is_infinite() {
|
|
return "inf".to_string();
|
|
} else {
|
|
return "NaN".to_string();
|
|
}
|
|
}
|
|
|
|
let s = format!("{:.*e}", precision.saturating_sub(1), value);
|
|
let parsed = s.parse::<f64>().unwrap_or(value);
|
|
|
|
// I would love to use the std library. There is not a speed concern here
|
|
// problem is it doesn't do the right thing
|
|
// Also ryu is my favorite _modern_ algorithm
|
|
let mut buffer = ryu::Buffer::new();
|
|
let text = buffer.format(parsed);
|
|
// The above algorithm converts 2 to 2.0 regrettably
|
|
if let Some(stripped) = text.strip_suffix(".0") {
|
|
return stripped.to_string();
|
|
}
|
|
text.to_string()
|
|
}
|
|
|
|
pub fn format_number(value: f64, format_code: &str, locale: &str) -> Formatted {
|
|
let locale = match get_locale(locale) {
|
|
Ok(l) => l,
|
|
Err(_) => {
|
|
return Formatted {
|
|
text: "#ERROR!".to_owned(),
|
|
color: None,
|
|
error: Some("Invalid locale".to_string()),
|
|
}
|
|
}
|
|
};
|
|
formatter::format::format_number(value, format_code, locale)
|
|
}
|