FIX: Format numbers a tad better
I still think there is some way to go, but this is closer to Excel
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
acb90fbb9d
commit
dc49afa2c3
@@ -15,7 +15,7 @@ pub struct Formatted {
|
||||
|
||||
/// Returns the vector of chars of the fractional part of a *positive* number:
|
||||
/// 3.1415926 ==> ['1', '4', '1', '5', '9', '2', '6']
|
||||
fn get_fract_part(value: f64, precision: i32) -> Vec<char> {
|
||||
fn get_fract_part(value: f64, precision: i32, int_len: usize) -> Vec<char> {
|
||||
let b = format!("{:.1$}", value.fract(), precision as usize)
|
||||
.chars()
|
||||
.collect::<Vec<char>>();
|
||||
@@ -30,6 +30,12 @@ fn get_fract_part(value: f64, precision: i32) -> Vec<char> {
|
||||
if last_non_zero < 2 {
|
||||
return vec![];
|
||||
}
|
||||
let max_len = if int_len > 15 {
|
||||
2_usize
|
||||
} else {
|
||||
15_usize - int_len + 1
|
||||
};
|
||||
let last_non_zero = usize::min(last_non_zero, max_len + 1);
|
||||
b[2..last_non_zero].to_vec()
|
||||
}
|
||||
|
||||
@@ -423,7 +429,7 @@ pub fn format_number(value_original: f64, format: &str, locale: &Locale) -> Form
|
||||
if value_abs as i64 == 0 {
|
||||
int_part = vec![];
|
||||
}
|
||||
let fract_part = get_fract_part(value_abs, p.precision);
|
||||
let fract_part = get_fract_part(value_abs, p.precision, int_part.len());
|
||||
// ln is the number of digits of the integer part of the value
|
||||
let ln = int_part.len() as i32;
|
||||
// digit count is the number of digit tokens ('0', '?' and '#') to the left of the decimal point
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#[cfg(feature = "use_regex_lite")]
|
||||
use regex_lite as regex;
|
||||
|
||||
use crate::{calc_result::CalcResult, expressions::token::is_english_error_string};
|
||||
use crate::{
|
||||
calc_result::CalcResult, expressions::token::is_english_error_string,
|
||||
number_format::to_excel_precision,
|
||||
};
|
||||
|
||||
/// This test for exact match (modulo case).
|
||||
/// * strings are not cast into bools or numbers
|
||||
@@ -34,6 +37,8 @@ pub(crate) fn values_are_equal(left: &CalcResult, right: &CalcResult) -> bool {
|
||||
pub(crate) fn compare_values(left: &CalcResult, right: &CalcResult) -> i32 {
|
||||
match (left, right) {
|
||||
(CalcResult::Number(value1), CalcResult::Number(value2)) => {
|
||||
let value1 = to_excel_precision(*value1, 15);
|
||||
let value2 = to_excel_precision(*value2, 15);
|
||||
if (value2 - value1).abs() < f64::EPSILON {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -112,29 +112,36 @@ pub fn to_precision(value: f64, precision: usize) -> f64 {
|
||||
/// ```
|
||||
/// This intends to be equivalent to the js: `${parseFloat(value.toPrecision(precision)})`
|
||||
/// See ([ecma](https://tc39.es/ecma262/#sec-number.prototype.toprecision)).
|
||||
/// FIXME: There has to be a better algorithm :/
|
||||
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_infinite() {
|
||||
return "inf".to_string();
|
||||
if !value.is_finite() {
|
||||
if value.is_infinite() {
|
||||
return "inf".to_string();
|
||||
} else {
|
||||
return "NaN".to_string();
|
||||
}
|
||||
}
|
||||
if value.is_nan() {
|
||||
return "NaN".to_string();
|
||||
}
|
||||
let exponent = value.abs().log10().floor();
|
||||
let base = value / 10.0_f64.powf(exponent);
|
||||
let base = format!("{0:.1$}", base, precision - 1);
|
||||
let value = format!("{base}e{exponent}").parse::<f64>().unwrap_or({
|
||||
// TODO: do this in a way that does not require a possible error
|
||||
0.0
|
||||
});
|
||||
|
||||
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(value);
|
||||
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();
|
||||
|
||||
@@ -8,6 +8,15 @@ fn test_simple_format() {
|
||||
assert_eq!(formatted.text, "2.3".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maximum_zeros() {
|
||||
let formatted = format_number(1.0 / 3.0, "#,##0.0000000000000000000", "en");
|
||||
assert_eq!(formatted.text, "0.3333333333333330000".to_string());
|
||||
|
||||
let formatted = format_number(1234.0 + 1.0 / 3.0, "#,##0.0000000000000000000", "en");
|
||||
assert_eq!(formatted.text, "1,234.3333333333300000000".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "not yet implemented"]
|
||||
fn test_wrong_locale() {
|
||||
|
||||
@@ -96,3 +96,14 @@ fn test_fn_tan_pi2() {
|
||||
// This is consistent with IEEE 754 but inconsistent with Excel
|
||||
assert_eq!(model._get_text("A1"), *"1.63312E+16");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trigonometric_identity() {
|
||||
let mut model = new_empty_model();
|
||||
model._set("A1", "=COTH(1)*CSCH(1)");
|
||||
model._set("A2", "=COSH(1)/(SINH(1))^2");
|
||||
model._set("A3", "=A1=A2");
|
||||
model.evaluate();
|
||||
|
||||
assert_eq!(model._get_text("A3"), *"TRUE");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user