1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq)]
5pub enum Value {
6 String(String),
8 U8(u8),
10 U16(u16),
12 U32(u32),
14 I16(i16),
16 I32(i32),
18 URational(u32, u32),
20 IRational(i32, i32),
22 F32(f32),
24 F64(f64),
26 Binary(Vec<u8>),
28 List(Vec<Value>),
30 Undefined(Vec<u8>),
32}
33
34impl Value {
35 pub fn to_display_string(&self) -> String {
37 match self {
38 Value::String(s) => s.clone(),
39 Value::U8(v) => v.to_string(),
40 Value::U16(v) => v.to_string(),
41 Value::U32(v) => v.to_string(),
42 Value::I16(v) => v.to_string(),
43 Value::I32(v) => v.to_string(),
44 Value::URational(n, d) => {
45 if *d == 0 {
46 if *n == 0 {
47 "undef".to_string()
48 } else {
49 "inf".to_string()
50 }
51 } else if *n % *d == 0 {
52 (*n / *d).to_string()
53 } else {
54 format_g_prec(*n as f64 / *d as f64, 10)
57 }
58 }
59 Value::IRational(n, d) => {
60 if *d == 0 {
61 if *n >= 0 {
62 "inf".to_string()
63 } else {
64 "-inf".to_string()
65 }
66 } else if *n % *d == 0 {
67 (*n / *d).to_string()
68 } else {
69 format_g_prec(*n as f64 / *d as f64, 10)
70 }
71 }
72 Value::F32(v) => format!("{}", v),
73 Value::F64(v) => format_g15(*v),
75 Value::Binary(data) => {
76 format!(
77 "(Binary data {} bytes, use -b option to extract)",
78 data.len()
79 )
80 }
81 Value::List(items) => {
82 let numeric = items.iter().all(|v| {
85 matches!(
86 v,
87 Value::U8(_)
88 | Value::U16(_)
89 | Value::U32(_)
90 | Value::I16(_)
91 | Value::I32(_)
92 | Value::URational(..)
93 | Value::IRational(..)
94 | Value::F32(_)
95 | Value::F64(_)
96 )
97 });
98 let sep = if numeric { " " } else { ", " };
99 items
100 .iter()
101 .map(|v| v.to_display_string())
102 .collect::<Vec<_>>()
103 .join(sep)
104 }
105 Value::Undefined(data) => {
106 format!(
107 "(Binary data {} bytes, use -b option to extract)",
108 data.len()
109 )
110 }
111 }
112 }
113
114 pub fn as_f64(&self) -> Option<f64> {
116 match self {
117 Value::U8(v) => Some(*v as f64),
118 Value::U16(v) => Some(*v as f64),
119 Value::U32(v) => Some(*v as f64),
120 Value::I16(v) => Some(*v as f64),
121 Value::I32(v) => Some(*v as f64),
122 Value::F32(v) => Some(*v as f64),
123 Value::F64(v) => Some(*v),
124 Value::URational(n, d) if *d != 0 => Some(*n as f64 / *d as f64),
125 Value::IRational(n, d) if *d != 0 => Some(*n as f64 / *d as f64),
126 _ => None,
127 }
128 }
129
130 pub fn as_str(&self) -> Option<&str> {
132 match self {
133 Value::String(s) => Some(s),
134 _ => None,
135 }
136 }
137
138 pub fn as_u64(&self) -> Option<u64> {
140 match self {
141 Value::U8(v) => Some(*v as u64),
142 Value::U16(v) => Some(*v as u64),
143 Value::U32(v) => Some(*v as u64),
144 _ => None,
145 }
146 }
147}
148
149impl fmt::Display for Value {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 write!(f, "{}", self.to_display_string())
152 }
153}
154
155pub fn format_g15(v: f64) -> String {
158 format_g_prec(v, 15)
159}
160
161pub fn format_g_prec(v: f64, prec: usize) -> String {
164 if v == 0.0 {
165 return "0".to_string();
166 }
167 let abs_v = v.abs();
168 let exp = abs_v.log10().floor() as i32;
169 if exp >= -4 && exp < prec as i32 {
170 let decimal_places = ((prec as i32 - 1 - exp).max(0)) as usize;
172 let s = format!("{:.prec$}", v, prec = decimal_places);
173 if s.contains('.') {
174 s.trim_end_matches('0').trim_end_matches('.').to_string()
175 } else {
176 s
177 }
178 } else {
179 let decimal_places = prec - 1;
181 let s = format!("{:.prec$e}", v, prec = decimal_places);
182 let (mantissa_part, exp_part) = if let Some(e_pos) = s.find('e') {
186 (&s[..e_pos], &s[e_pos + 1..])
187 } else {
188 return s;
189 };
190 let mantissa_trimmed = if mantissa_part.contains('.') {
191 mantissa_part.trim_end_matches('0').trim_end_matches('.')
192 } else {
193 mantissa_part
194 };
195 let exp_val: i32 = exp_part.parse().unwrap_or(0);
197 let exp_str = if exp_val >= 0 {
198 format!("e+{:02}", exp_val)
199 } else {
200 format!("e-{:02}", -exp_val)
201 };
202 format!("{}{}", mantissa_trimmed, exp_str)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
213 fn display_string() {
214 assert_eq!(Value::String("hello".into()).to_display_string(), "hello");
215 }
216
217 #[test]
218 fn display_u8() {
219 assert_eq!(Value::U8(42).to_display_string(), "42");
220 }
221
222 #[test]
223 fn display_u16() {
224 assert_eq!(Value::U16(1024).to_display_string(), "1024");
225 }
226
227 #[test]
228 fn display_u32() {
229 assert_eq!(Value::U32(100_000).to_display_string(), "100000");
230 }
231
232 #[test]
233 fn display_i16() {
234 assert_eq!(Value::I16(-123).to_display_string(), "-123");
235 }
236
237 #[test]
238 fn display_i32() {
239 assert_eq!(Value::I32(-999_999).to_display_string(), "-999999");
240 }
241
242 #[test]
243 fn display_urational_exact_division() {
244 assert_eq!(Value::URational(100, 10).to_display_string(), "10");
245 }
246
247 #[test]
248 fn display_urational_non_exact() {
249 assert_eq!(Value::URational(1, 3).to_display_string(), "0.3333333333");
251 }
252
253 #[test]
254 fn display_urational_zero_zero() {
255 assert_eq!(Value::URational(0, 0).to_display_string(), "undef");
256 }
257
258 #[test]
259 fn display_urational_n_over_zero() {
260 assert_eq!(Value::URational(5, 0).to_display_string(), "inf");
261 }
262
263 #[test]
264 fn display_irational_exact() {
265 assert_eq!(Value::IRational(-10, 5).to_display_string(), "-2");
266 }
267
268 #[test]
269 fn display_irational_non_exact() {
270 assert_eq!(Value::IRational(7, 3).to_display_string(), "2.333333333");
271 }
272
273 #[test]
274 fn display_irational_positive_inf() {
275 assert_eq!(Value::IRational(1, 0).to_display_string(), "inf");
276 }
277
278 #[test]
279 fn display_irational_zero_inf() {
280 assert_eq!(Value::IRational(0, 0).to_display_string(), "inf");
282 }
283
284 #[test]
285 fn display_irational_negative_inf() {
286 assert_eq!(Value::IRational(-3, 0).to_display_string(), "-inf");
287 }
288
289 #[test]
290 fn display_f32() {
291 let s = Value::F32(2.75).to_display_string();
292 assert!(s.starts_with("2.75"), "got: {}", s);
293 }
294
295 #[test]
296 fn display_f64() {
297 assert_eq!(Value::F64(2.5).to_display_string(), "2.5");
298 }
299
300 #[test]
301 fn display_binary() {
302 assert_eq!(
303 Value::Binary(vec![0, 1, 2]).to_display_string(),
304 "(Binary data 3 bytes, use -b option to extract)"
305 );
306 }
307
308 #[test]
309 fn display_list() {
310 let list = Value::List(vec![Value::U16(640), Value::U16(480)]);
312 assert_eq!(list.to_display_string(), "640 480");
313 }
314
315 #[test]
316 fn display_undefined() {
317 assert_eq!(
318 Value::Undefined(vec![0xAB; 5]).to_display_string(),
319 "(Binary data 5 bytes, use -b option to extract)"
320 );
321 }
322
323 #[test]
326 fn as_f64_u8() {
327 assert_eq!(Value::U8(10).as_f64(), Some(10.0));
328 }
329
330 #[test]
331 fn as_f64_u16() {
332 assert_eq!(Value::U16(300).as_f64(), Some(300.0));
333 }
334
335 #[test]
336 fn as_f64_u32() {
337 assert_eq!(Value::U32(70_000).as_f64(), Some(70_000.0));
338 }
339
340 #[test]
341 fn as_f64_i16() {
342 assert_eq!(Value::I16(-50).as_f64(), Some(-50.0));
343 }
344
345 #[test]
346 fn as_f64_i32() {
347 assert_eq!(Value::I32(-1_000_000).as_f64(), Some(-1_000_000.0));
348 }
349
350 #[test]
351 fn as_f64_f32() {
352 let val = Value::F32(1.5).as_f64().unwrap();
353 assert!((val - 1.5).abs() < 1e-6);
354 }
355
356 #[test]
357 fn as_f64_f64() {
358 assert_eq!(Value::F64(9.99).as_f64(), Some(9.99));
359 }
360
361 #[test]
362 fn as_f64_urational() {
363 let val = Value::URational(1, 4).as_f64().unwrap();
364 assert!((val - 0.25).abs() < 1e-10);
365 }
366
367 #[test]
368 fn as_f64_urational_zero_denom() {
369 assert_eq!(Value::URational(5, 0).as_f64(), None);
370 }
371
372 #[test]
373 fn as_f64_irational() {
374 let val = Value::IRational(-3, 2).as_f64().unwrap();
375 assert!((val - -1.5).abs() < 1e-10);
376 }
377
378 #[test]
379 fn as_f64_irational_zero_denom() {
380 assert_eq!(Value::IRational(-1, 0).as_f64(), None);
381 }
382
383 #[test]
384 fn as_f64_string_none() {
385 assert_eq!(Value::String("hi".into()).as_f64(), None);
386 }
387
388 #[test]
389 fn as_f64_binary_none() {
390 assert_eq!(Value::Binary(vec![1]).as_f64(), None);
391 }
392
393 #[test]
394 fn as_f64_undefined_none() {
395 assert_eq!(Value::Undefined(vec![1]).as_f64(), None);
396 }
397
398 #[test]
401 fn as_str_string() {
402 assert_eq!(Value::String("test".into()).as_str(), Some("test"));
403 }
404
405 #[test]
406 fn as_str_non_string() {
407 assert_eq!(Value::U8(1).as_str(), None);
408 assert_eq!(Value::Binary(vec![]).as_str(), None);
409 assert_eq!(Value::F64(1.0).as_str(), None);
410 }
411
412 #[test]
415 fn as_u64_unsigned_types() {
416 assert_eq!(Value::U8(255).as_u64(), Some(255));
417 assert_eq!(Value::U16(65535).as_u64(), Some(65535));
418 assert_eq!(Value::U32(0xFFFFFFFF).as_u64(), Some(0xFFFFFFFF));
419 }
420
421 #[test]
422 fn as_u64_signed_none() {
423 assert_eq!(Value::I16(1).as_u64(), None);
424 assert_eq!(Value::I32(1).as_u64(), None);
425 }
426
427 #[test]
428 fn as_u64_other_none() {
429 assert_eq!(Value::String("42".into()).as_u64(), None);
430 assert_eq!(Value::F64(1.0).as_u64(), None);
431 assert_eq!(Value::Binary(vec![]).as_u64(), None);
432 assert_eq!(Value::Undefined(vec![]).as_u64(), None);
433 }
434
435 #[test]
438 fn display_trait_delegates() {
439 let v = Value::URational(1, 3);
440 assert_eq!(format!("{}", v), "0.3333333333");
441 }
442
443 #[test]
446 fn format_g15_zero() {
447 assert_eq!(format_g15(0.0), "0");
448 }
449
450 #[test]
451 fn format_g15_integer() {
452 assert_eq!(format_g15(42.0), "42");
453 }
454
455 #[test]
456 fn format_g15_simple_decimal() {
457 assert_eq!(format_g15(3.5), "3.5");
458 }
459
460 #[test]
461 fn format_g15_negative() {
462 assert_eq!(format_g15(-1.25), "-1.25");
463 }
464
465 #[test]
466 fn format_g15_large_value_scientific() {
467 let s = format_g15(1e20);
469 assert!(s.contains("e+"), "expected scientific notation, got: {}", s);
470 }
471
472 #[test]
473 fn format_g15_small_value_scientific() {
474 let s = format_g15(1e-5);
476 assert!(s.contains("e-"), "expected scientific notation, got: {}", s);
477 }
478
479 #[test]
480 fn format_g15_borderline_fixed() {
481 let s = format_g15(0.0001);
483 assert_eq!(s, "0.0001");
484 }
485
486 #[test]
487 fn format_g_prec_low_precision() {
488 let s = format_g_prec(std::f64::consts::PI, 3);
490 assert_eq!(s, "3.14");
491 }
492
493 #[test]
494 fn format_g_prec_one_digit() {
495 let s = format_g_prec(7.7, 1);
496 assert_eq!(s, "8");
497 }
498
499 #[test]
500 fn format_g15_trailing_zeros_stripped() {
501 let s = format_g15(1.5);
503 assert!(!s.ends_with('0'), "trailing zeros not stripped: {}", s);
504 }
505
506 #[test]
507 fn format_g15_very_large() {
508 let s = format_g15(1.23456789e+100);
509 assert!(s.starts_with("1.23456789"), "got: {}", s);
510 assert!(s.contains("e+100"), "got: {}", s);
511 }
512
513 #[test]
514 fn format_g15_very_small_negative() {
515 let s = format_g15(-5.5e-10);
516 assert!(s.starts_with("-5.5e-"), "got: {}", s);
517 }
518}