1use crate::intrinsics::fold;
20use crate::rows;
21use crate::sheet::number_text;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub enum ErrorKind {
26 Null,
28 Div0,
30 Value,
32 Ref,
34 Name,
36 Num,
38 NA,
40}
41
42impl ErrorKind {
43 pub fn text(self) -> &'static str {
45 match self {
46 ErrorKind::Null => "#NULL!",
47 ErrorKind::Div0 => "#DIV/0!",
48 ErrorKind::Value => "#VALUE!",
49 ErrorKind::Ref => "#REF!",
50 ErrorKind::Name => "#NAME?",
51 ErrorKind::Num => "#NUM!",
52 ErrorKind::NA => "#N/A",
53 }
54 }
55
56 pub fn parse(text: &str) -> Option<ErrorKind> {
59 match fold(text.trim()).as_str() {
60 "#null!" => Some(ErrorKind::Null),
61 "#div/0!" => Some(ErrorKind::Div0),
62 "#value!" => Some(ErrorKind::Value),
63 "#ref!" => Some(ErrorKind::Ref),
64 "#name?" => Some(ErrorKind::Name),
65 "#num!" => Some(ErrorKind::Num),
66 "#n/a" => Some(ErrorKind::NA),
67 _ => None,
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq)]
74pub enum Value {
75 Number(f64),
76 Text(String),
77 Bool(bool),
78 Error(ErrorKind),
79 Empty,
83}
84
85impl Value {
86 pub fn number(&self) -> Result<f64, ErrorKind> {
93 match self {
94 Value::Number(v) => Ok(*v),
95 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
96 Value::Empty => Ok(0.0),
97 Value::Text(t) => text_to_number(t).ok_or(ErrorKind::Value),
98 Value::Error(k) => Err(*k),
99 }
100 }
101
102 pub fn text(&self) -> Result<String, ErrorKind> {
106 match self {
107 Value::Text(t) => Ok(t.clone()),
108 Value::Number(v) => Ok(number_to_text(*v)),
109 Value::Bool(true) => Ok("TRUE".to_string()),
110 Value::Bool(false) => Ok("FALSE".to_string()),
111 Value::Empty => Ok(String::new()),
112 Value::Error(k) => Err(*k),
113 }
114 }
115
116 pub fn truth(&self) -> Result<bool, ErrorKind> {
121 match self {
122 Value::Bool(b) => Ok(*b),
123 Value::Number(v) => Ok(*v != 0.0),
124 Value::Empty => Ok(false),
125 Value::Text(t) => match fold(t).as_str() {
126 "true" => Ok(true),
127 "false" => Ok(false),
128 _ => Err(ErrorKind::Value),
129 },
130 Value::Error(k) => Err(*k),
131 }
132 }
133
134 pub fn settled(self) -> Value {
137 match self {
138 Value::Empty => Value::Number(0.0),
139 other => other,
140 }
141 }
142
143 pub fn spell(&self) -> String {
148 match self {
149 Value::Number(v) if *v == 0.0 => "0".to_string(),
150 Value::Number(v) => match number_text(*v) {
151 Some(text) => text,
152 None => rows::datum(&rows::Value::Error(ErrorKind::Num.text().to_string())),
153 },
154 Value::Text(t) => rows::quoted(t),
155 Value::Bool(true) => "true".to_string(),
156 Value::Bool(false) => "false".to_string(),
157 Value::Error(k) => rows::datum(&rows::Value::Error(k.text().to_string())),
158 Value::Empty => "0".to_string(),
159 }
160 }
161
162 pub fn from_file(v: &rows::Value) -> Option<Value> {
168 match v {
169 rows::Value::Number(text) => {
170 let n: f64 = text.trim().parse().ok()?;
171 n.is_finite().then_some(Value::Number(n))
172 }
173 rows::Value::Text(t) => Some(Value::Text(t.clone())),
174 rows::Value::Bool(b) => Some(Value::Bool(*b)),
175 rows::Value::Error(text) => ErrorKind::parse(text).map(Value::Error),
176 rows::Value::Date(_) => None,
177 }
178 }
179}
180
181pub fn round15(v: f64) -> f64 {
185 if v == 0.0 || !v.is_finite() {
186 return v;
187 }
188 format!("{v:.14e}").parse().unwrap_or(v)
189}
190
191pub fn number_to_text(v: f64) -> String {
203 if v == 0.0 {
204 return "0".to_string();
205 }
206 let r = round15(v);
207 let sci = format!("{r:.14e}");
208 let (mantissa, exponent) = sci.split_once('e').unwrap_or((sci.as_str(), "0"));
209 let exponent: i32 = exponent.parse().unwrap_or(0);
210 if (-14..=14).contains(&exponent) {
211 return format!("{r}");
215 }
216 let mantissa = mantissa.trim_end_matches('0').trim_end_matches('.');
217 format!(
218 "{mantissa}E{}{:02}",
219 if exponent < 0 { '-' } else { '+' },
220 exponent.abs()
221 )
222}
223
224pub fn text_to_number(text: &str) -> Option<f64> {
230 let t = text.trim();
231 let (t, percent) = match t.strip_suffix('%') {
232 Some(rest) => (rest.trim_end(), true),
233 None => (t, false),
234 };
235 let bytes = t.as_bytes();
236 if bytes.is_empty() {
237 return None;
238 }
239 let mut i = 0;
240 if bytes[i] == b'+' || bytes[i] == b'-' {
241 i += 1;
242 }
243 let mut digits = 0;
244 let mut dot = false;
245 while i < bytes.len() {
246 match bytes[i] {
247 b'0'..=b'9' => digits += 1,
248 b'.' if !dot => dot = true,
249 _ => break,
250 }
251 i += 1;
252 }
253 if digits == 0 {
254 return None;
255 }
256 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
257 let mut j = i + 1;
258 if j < bytes.len() && (bytes[j] == b'+' || bytes[j] == b'-') {
259 j += 1;
260 }
261 let first = j;
262 while j < bytes.len() && bytes[j].is_ascii_digit() {
263 j += 1;
264 }
265 if j == first {
266 return None;
267 }
268 i = j;
269 }
270 if i != bytes.len() {
271 return None;
272 }
273 let v: f64 = t.parse().ok()?;
274 let v = if percent { v / 100.0 } else { v };
275 v.is_finite().then_some(v)
276}
277
278pub fn agrees(computed: &Value, cached: &rows::Value) -> Option<bool> {
284 let cached = Value::from_file(cached)?;
285 let computed = computed.clone().settled();
286 Some(match (&computed, &cached) {
287 (Value::Number(a), Value::Number(b)) => {
288 let key = |x: f64| format!("{:.14e}", if x == 0.0 { 0.0 } else { round15(x) });
289 key(*a) == key(*b)
290 }
291 (Value::Text(a), Value::Text(b)) => a == b,
292 (Value::Bool(a), Value::Bool(b)) => a == b,
293 (Value::Error(a), Value::Error(b)) => a == b,
294 _ => false,
295 })
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn the_seven_errors_spell_and_parse() {
304 for k in [
305 ErrorKind::Null,
306 ErrorKind::Div0,
307 ErrorKind::Value,
308 ErrorKind::Ref,
309 ErrorKind::Name,
310 ErrorKind::Num,
311 ErrorKind::NA,
312 ] {
313 assert_eq!(ErrorKind::parse(k.text()), Some(k));
314 assert_eq!(ErrorKind::parse(&k.text().to_lowercase()), Some(k));
315 }
316 assert_eq!(ErrorKind::parse("#SPILL!"), None);
317 }
318
319 #[test]
320 fn numbers_are_read_as_excel_reads_an_operand() {
321 assert_eq!(Value::Text("5".into()).number(), Ok(5.0));
322 assert_eq!(Value::Text(" 1e3 ".into()).number(), Ok(1000.0));
323 assert_eq!(Value::Text("50%".into()).number(), Ok(0.5));
324 assert_eq!(Value::Text("-.5".into()).number(), Ok(-0.5));
325 assert_eq!(Value::Text("abc".into()).number(), Err(ErrorKind::Value));
326 assert_eq!(Value::Text("".into()).number(), Err(ErrorKind::Value));
327 assert_eq!(Value::Text("1,000".into()).number(), Err(ErrorKind::Value));
328 assert_eq!(Value::Bool(true).number(), Ok(1.0));
329 assert_eq!(Value::Empty.number(), Ok(0.0));
330 assert_eq!(Value::Error(ErrorKind::NA).number(), Err(ErrorKind::NA));
331 }
332
333 #[test]
334 fn texts_are_excel_s_general_spelling() {
335 assert_eq!(number_to_text(15.0), "15");
336 assert_eq!(number_to_text(0.1 + 0.2), "0.3");
337 assert_eq!(number_to_text(1.0 / 3.0), "0.333333333333333");
338 assert_eq!(number_to_text(-2.5), "-2.5");
339 assert_eq!(number_to_text(0.000001), "0.000001");
340 assert_eq!(number_to_text(0.0000001), "0.0000001");
343 assert_eq!(number_to_text(1e20), "1E+20");
344 assert_eq!(number_to_text(1e21), "1E+21");
345 assert_eq!(number_to_text(1e14), "100000000000000");
348 assert_eq!(number_to_text(1e15), "1E+15");
349 assert_eq!(number_to_text(999_999_999_999_999.0), "999999999999999");
350 assert_eq!(number_to_text(999_999_999_999_999.9), "1E+15");
351 assert_eq!(number_to_text(2f64.powi(57)), "1.44115188075856E+17");
352 assert_eq!(number_to_text(1e-14), "0.00000000000001");
353 assert_eq!(number_to_text(1e-15), "1E-15");
354 assert_eq!(number_to_text(1.5e-7), "0.00000015");
355 assert_eq!(number_to_text(-0.0000001), "-0.0000001");
356 assert_eq!(
357 number_to_text(1.0 / 3.0 * 1e-10),
358 "0.0000000000333333333333333"
359 );
360 assert_eq!(number_to_text(2f64.sqrt()), "1.4142135623731");
361 assert_eq!(number_to_text(0.0), "0");
362 assert_eq!(Value::Bool(true).text(), Ok("TRUE".to_string()));
363 assert_eq!(Value::Empty.text(), Ok(String::new()));
364 }
365
366 #[test]
367 fn truth_is_read_from_numbers_and_the_two_words() {
368 assert_eq!(Value::Number(2.0).truth(), Ok(true));
369 assert_eq!(Value::Number(0.0).truth(), Ok(false));
370 assert_eq!(Value::Text("true".into()).truth(), Ok(true));
371 assert_eq!(Value::Text("yes".into()).truth(), Err(ErrorKind::Value));
372 assert_eq!(Value::Empty.truth(), Ok(false));
373 }
374
375 #[test]
376 fn a_value_spells_as_a_cell_row_does() {
377 assert_eq!(Value::Number(15.0).spell(), "15");
378 assert_eq!(Value::Number(0.6).spell(), "0.6");
379 assert_eq!(Value::Text("big".into()).spell(), "\"big\"");
380 assert_eq!(Value::Bool(false).spell(), "false");
381 assert_eq!(Value::Error(ErrorKind::Div0).spell(), "(error \"#DIV/0!\")");
382 assert_eq!(Value::Number(f64::INFINITY).spell(), "(error \"#NUM!\")");
383 assert_eq!(Value::Empty.spell(), "0");
384 assert_eq!(Value::Empty.settled(), Value::Number(0.0));
385 }
386
387 #[test]
388 fn agreement_carries_the_stated_tolerance_and_nothing_more() {
389 let cached = |s: &str| rows::Value::Number(s.to_string());
390 assert_eq!(
391 agrees(&Value::Number(0.1 + 0.2), &cached("0.3")),
392 Some(true)
393 );
394 assert_eq!(
395 agrees(&Value::Number(0.1 + 0.2), &cached("0.30000000000000004")),
396 Some(true)
397 );
398 assert_eq!(agrees(&Value::Number(15.0), &cached("15")), Some(true));
399 assert_eq!(
400 agrees(&Value::Number(15.0), &cached("15.000000000001")),
401 Some(false)
402 );
403 assert_eq!(agrees(&Value::Empty, &cached("0")), Some(true));
404 assert_eq!(
405 agrees(&Value::Text("big".into()), &rows::Value::Text("big".into())),
406 Some(true)
407 );
408 assert_eq!(
409 agrees(&Value::Text("big".into()), &rows::Value::Text("Big".into())),
410 Some(false)
411 );
412 assert_eq!(
413 agrees(
414 &Value::Error(ErrorKind::Div0),
415 &rows::Value::Error("#DIV/0!".into())
416 ),
417 Some(true)
418 );
419 assert_eq!(
420 agrees(&Value::Number(1.0), &rows::Value::Date("2026-10-08".into())),
421 None
422 );
423 assert_eq!(
424 agrees(&Value::Number(1.0), &rows::Value::Bool(true)),
425 Some(false)
426 );
427 }
428}