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