1use std::io::Write;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ScaleUnit {
6 None,
8 Si,
10 Iec,
12 IecI,
14 Auto,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum RoundMethod {
21 Up,
23 Down,
25 FromZero,
27 TowardsZero,
29 Nearest,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum InvalidMode {
36 Abort,
38 Fail,
40 Warn,
42 Ignore,
44}
45
46struct FieldSet {
51 bits: u128,
52 all: bool,
53 overflow: Vec<usize>,
54}
55
56impl FieldSet {
57 fn from_config(field: &[usize]) -> Self {
58 if field.is_empty() {
59 return FieldSet {
60 bits: 0,
61 all: true,
62 overflow: Vec::new(),
63 };
64 }
65 let mut bits: u128 = 0;
66 let mut overflow = Vec::new();
67 for &f in field {
68 if f >= 1 && f <= 128 {
69 bits |= 1u128 << (f - 1);
70 } else if f > 128 {
71 overflow.push(f);
72 }
73 }
74 FieldSet {
75 bits,
76 all: false,
77 overflow,
78 }
79 }
80
81 #[inline(always)]
82 fn contains(&self, field_num: usize) -> bool {
83 if self.all {
84 return true;
85 }
86 if field_num >= 1 && field_num <= 128 {
87 (self.bits & (1u128 << (field_num - 1))) != 0
88 } else {
89 self.overflow.contains(&field_num)
90 }
91 }
92}
93
94pub struct NumfmtConfig {
96 pub from: ScaleUnit,
97 pub to: ScaleUnit,
98 pub from_unit: f64,
99 pub to_unit: f64,
100 pub padding: Option<i32>,
101 pub round: RoundMethod,
102 pub suffix: Option<String>,
103 pub format: Option<String>,
104 pub field: Vec<usize>,
105 pub delimiter: Option<char>,
106 pub header: usize,
107 pub invalid: InvalidMode,
108 pub grouping: bool,
109 pub zero_terminated: bool,
110}
111
112impl Default for NumfmtConfig {
113 fn default() -> Self {
114 Self {
115 from: ScaleUnit::None,
116 to: ScaleUnit::None,
117 from_unit: 1.0,
118 to_unit: 1.0,
119 padding: None,
120 round: RoundMethod::FromZero,
121 suffix: None,
122 format: None,
123 field: vec![1],
124 delimiter: None,
125 header: 0,
126 invalid: InvalidMode::Abort,
127 grouping: false,
128 zero_terminated: false,
129 }
130 }
131}
132
133const SI_SUFFIXES: &[(char, f64)] = &[
136 ('K', 1e3),
137 ('M', 1e6),
138 ('G', 1e9),
139 ('T', 1e12),
140 ('P', 1e15),
141 ('E', 1e18),
142 ('Z', 1e21),
143 ('Y', 1e24),
144 ('R', 1e27),
145 ('Q', 1e30),
146];
147
148const IEC_SUFFIXES: &[(char, f64)] = &[
150 ('K', 1024.0),
151 ('M', 1_048_576.0),
152 ('G', 1_073_741_824.0),
153 ('T', 1_099_511_627_776.0),
154 ('P', 1_125_899_906_842_624.0),
155 ('E', 1_152_921_504_606_846_976.0),
156 ('Z', 1_180_591_620_717_411_303_424.0),
157 ('Y', 1_208_925_819_614_629_174_706_176.0),
158 ('R', 1_237_940_039_285_380_274_899_124_224.0),
159 ('Q', 1_267_650_600_228_229_401_496_703_205_376.0),
160];
161
162pub fn parse_scale_unit(s: &str) -> Result<ScaleUnit, String> {
164 match s {
165 "none" => Ok(ScaleUnit::None),
166 "si" => Ok(ScaleUnit::Si),
167 "iec" => Ok(ScaleUnit::Iec),
168 "iec-i" => Ok(ScaleUnit::IecI),
169 "auto" => Ok(ScaleUnit::Auto),
170 _ => Err(format!("invalid unit: '{}'", s)),
171 }
172}
173
174pub fn parse_round_method(s: &str) -> Result<RoundMethod, String> {
176 match s {
177 "up" => Ok(RoundMethod::Up),
178 "down" => Ok(RoundMethod::Down),
179 "from-zero" => Ok(RoundMethod::FromZero),
180 "towards-zero" => Ok(RoundMethod::TowardsZero),
181 "nearest" => Ok(RoundMethod::Nearest),
182 _ => Err(format!("invalid rounding method: '{}'", s)),
183 }
184}
185
186pub fn parse_invalid_mode(s: &str) -> Result<InvalidMode, String> {
188 match s {
189 "abort" => Ok(InvalidMode::Abort),
190 "fail" => Ok(InvalidMode::Fail),
191 "warn" => Ok(InvalidMode::Warn),
192 "ignore" => Ok(InvalidMode::Ignore),
193 _ => Err(format!("invalid mode: '{}'", s)),
194 }
195}
196
197pub fn parse_fields(s: &str) -> Result<Vec<usize>, String> {
200 if s == "-" {
201 return Ok(vec![]);
203 }
204 let mut fields = Vec::new();
205 for part in s.split(',') {
206 let part = part.trim();
207 if let Some(dash_pos) = part.find('-') {
208 let start_str = &part[..dash_pos];
209 let end_str = &part[dash_pos + 1..];
210 if start_str.is_empty() && end_str.is_empty() {
212 return Ok(vec![]);
213 }
214 let start: usize = if start_str.is_empty() {
215 1
216 } else {
217 start_str
218 .parse()
219 .map_err(|_| format!("invalid field value '{}'", part))?
220 };
221 let end: usize = if end_str.is_empty() {
222 9999
225 } else {
226 end_str
227 .parse()
228 .map_err(|_| format!("invalid field value '{}'", part))?
229 };
230 if start == 0 {
231 return Err(format!("fields are numbered from 1: '{}'", part));
232 }
233 for i in start..=end {
234 if !fields.contains(&i) {
235 fields.push(i);
236 }
237 }
238 } else {
239 let n: usize = part
240 .parse()
241 .map_err(|_| format!("invalid field value '{}'", part))?;
242 if n == 0 {
243 return Err("fields are numbered from 1".to_string());
244 }
245 if !fields.contains(&n) {
246 fields.push(n);
247 }
248 }
249 }
250 fields.sort();
251 Ok(fields)
252}
253
254fn parse_number_with_suffix(s: &str, unit: ScaleUnit) -> Result<f64, String> {
257 let s = s.trim();
258 if s.is_empty() {
259 return Err("invalid number: ''".to_string());
260 }
261
262 let mut num_end = s.len();
264 let bytes = s.as_bytes();
265 let len = s.len();
266
267 if len > 0 {
269 let last_char = bytes[len - 1] as char;
270
271 match unit {
272 ScaleUnit::Auto | ScaleUnit::IecI => {
273 if last_char == 'i' && len >= 2 {
275 let prefix_char = (bytes[len - 2] as char).to_ascii_uppercase();
276 if is_scale_suffix(prefix_char) {
277 num_end = len - 2;
278 }
279 } else {
280 let upper = last_char.to_ascii_uppercase();
281 if is_scale_suffix(upper) {
282 num_end = len - 1;
283 }
284 }
285 }
286 ScaleUnit::Si | ScaleUnit::Iec => {
287 let upper = last_char.to_ascii_uppercase();
288 if is_scale_suffix(upper) {
289 num_end = len - 1;
290 }
291 }
292 ScaleUnit::None => {}
293 }
294 }
295
296 let num_str = &s[..num_end];
297 let suffix_str = &s[num_end..];
298
299 let value: f64 = num_str
301 .parse()
302 .map_err(|_| format!("invalid number: '{}'", s))?;
303
304 let multiplier = if suffix_str.is_empty() {
306 1.0
307 } else {
308 let suffix_upper = suffix_str.as_bytes()[0].to_ascii_uppercase() as char;
309 match unit {
310 ScaleUnit::Auto => {
311 if suffix_str.len() >= 2 && suffix_str.as_bytes()[suffix_str.len() - 1] == b'i' {
313 find_iec_multiplier(suffix_upper)?
314 } else {
315 find_si_multiplier(suffix_upper)?
316 }
317 }
318 ScaleUnit::Si => find_si_multiplier(suffix_upper)?,
319 ScaleUnit::Iec | ScaleUnit::IecI => find_iec_multiplier(suffix_upper)?,
320 ScaleUnit::None => {
321 return Err(format!("invalid number: '{}'", s));
322 }
323 }
324 };
325
326 Ok(value * multiplier)
327}
328
329#[inline(always)]
330fn is_scale_suffix(c: char) -> bool {
331 matches!(c, 'K' | 'M' | 'G' | 'T' | 'P' | 'E' | 'Z' | 'Y' | 'R' | 'Q')
332}
333
334fn find_si_multiplier(c: char) -> Result<f64, String> {
335 match c.to_ascii_uppercase() {
336 'K' => Ok(1e3),
337 'M' => Ok(1e6),
338 'G' => Ok(1e9),
339 'T' => Ok(1e12),
340 'P' => Ok(1e15),
341 'E' => Ok(1e18),
342 'Z' => Ok(1e21),
343 'Y' => Ok(1e24),
344 'R' => Ok(1e27),
345 'Q' => Ok(1e30),
346 _ => Err(format!("invalid suffix: '{}'", c)),
347 }
348}
349
350fn find_iec_multiplier(c: char) -> Result<f64, String> {
351 match c {
352 'K' => Ok(1024.0),
353 'M' => Ok(1_048_576.0),
354 'G' => Ok(1_073_741_824.0),
355 'T' => Ok(1_099_511_627_776.0),
356 'P' => Ok(1_125_899_906_842_624.0),
357 'E' => Ok(1_152_921_504_606_846_976.0),
358 'Z' => Ok(1_180_591_620_717_411_303_424.0),
359 'Y' => Ok(1_208_925_819_614_629_174_706_176.0),
360 'R' => Ok(1_237_940_039_285_380_274_899_124_224.0),
361 'Q' => Ok(1_267_650_600_228_229_401_496_703_205_376.0),
362 _ => Err(format!("invalid suffix: '{}'", c)),
363 }
364}
365
366#[inline(always)]
368fn apply_round(value: f64, method: RoundMethod) -> f64 {
369 match method {
370 RoundMethod::Up => value.ceil(),
371 RoundMethod::Down => value.floor(),
372 RoundMethod::FromZero => {
373 if value >= 0.0 {
374 value.ceil()
375 } else {
376 value.floor()
377 }
378 }
379 RoundMethod::TowardsZero => {
380 if value >= 0.0 {
381 value.floor()
382 } else {
383 value.ceil()
384 }
385 }
386 RoundMethod::Nearest => value.round(),
387 }
388}
389
390fn format_scaled(value: f64, unit: ScaleUnit, round: RoundMethod) -> String {
392 match unit {
393 ScaleUnit::None => {
394 format_plain_number(value)
396 }
397 ScaleUnit::Si => format_with_scale(value, SI_SUFFIXES, "", round),
398 ScaleUnit::Iec => format_with_scale(value, IEC_SUFFIXES, "", round),
399 ScaleUnit::IecI => format_with_scale(value, IEC_SUFFIXES, "i", round),
400 ScaleUnit::Auto => {
401 format_with_scale(value, SI_SUFFIXES, "", round)
403 }
404 }
405}
406
407fn write_scaled_to_buf(buf: &mut Vec<u8>, value: f64, unit: ScaleUnit, round: RoundMethod) {
409 match unit {
410 ScaleUnit::None => {
411 write_plain_number_to_buf(buf, value);
412 }
413 ScaleUnit::Si => write_with_scale_to_buf(buf, value, SI_SUFFIXES, b"", round),
414 ScaleUnit::Iec => write_with_scale_to_buf(buf, value, IEC_SUFFIXES, b"", round),
415 ScaleUnit::IecI => write_with_scale_to_buf(buf, value, IEC_SUFFIXES, b"i", round),
416 ScaleUnit::Auto => write_with_scale_to_buf(buf, value, SI_SUFFIXES, b"", round),
417 }
418}
419
420#[inline]
422fn write_plain_number_to_buf(buf: &mut Vec<u8>, value: f64) {
423 let int_val = value as i64;
424 if value == (int_val as f64) {
425 let mut itoa_buf = itoa::Buffer::new();
426 buf.extend_from_slice(itoa_buf.format(int_val).as_bytes());
427 } else {
428 use std::io::Write;
430 let _ = write!(buf, "{:.1}", value);
431 }
432}
433
434fn write_with_scale_to_buf(
436 buf: &mut Vec<u8>,
437 value: f64,
438 suffixes: &[(char, f64)],
439 i_suffix: &[u8],
440 round: RoundMethod,
441) {
442 let abs_value = value.abs();
443 let negative = value < 0.0;
444
445 let mut chosen_idx: Option<usize> = None;
447 for (idx, &(_suffix, mult)) in suffixes.iter().enumerate().rev() {
448 if abs_value >= mult {
449 chosen_idx = Some(idx);
450 break;
451 }
452 }
453
454 let Some(mut idx) = chosen_idx else {
455 write_plain_number_to_buf(buf, value);
457 return;
458 };
459
460 loop {
461 let (suffix, mult) = suffixes[idx];
462 let scaled = value / mult;
463 let abs_scaled = scaled.abs();
464
465 if abs_scaled < 10.0 {
466 let rounded = apply_round_for_display(scaled, round);
467 if rounded.abs() >= 10.0 {
468 let int_val = rounded as i64;
469 if int_val.unsigned_abs() >= 1000 && idx + 1 < suffixes.len() {
470 idx += 1;
471 continue;
472 }
473 if negative {
474 buf.push(b'-');
475 }
476 let mut itoa_buf = itoa::Buffer::new();
477 buf.extend_from_slice(itoa_buf.format(int_val.unsigned_abs()).as_bytes());
478 buf.push(suffix as u8);
479 buf.extend_from_slice(i_suffix);
480 return;
481 }
482 if negative {
483 buf.push(b'-');
484 }
485 let abs_rounded = rounded.abs();
487 let int_part = abs_rounded as u64;
488 let frac_part = ((abs_rounded - int_part as f64) * 10.0).round() as u8;
489 let mut itoa_buf = itoa::Buffer::new();
490 buf.extend_from_slice(itoa_buf.format(int_part).as_bytes());
491 buf.push(b'.');
492 buf.push(b'0' + frac_part);
493 buf.push(suffix as u8);
494 buf.extend_from_slice(i_suffix);
495 return;
496 } else {
497 let int_val = apply_round_int(scaled, round);
498 if int_val.unsigned_abs() >= 1000 {
499 if idx + 1 < suffixes.len() {
500 idx += 1;
501 continue;
502 }
503 }
504 if negative {
505 buf.push(b'-');
506 }
507 let mut itoa_buf = itoa::Buffer::new();
508 buf.extend_from_slice(itoa_buf.format(int_val.unsigned_abs()).as_bytes());
509 buf.push(suffix as u8);
510 buf.extend_from_slice(i_suffix);
511 return;
512 }
513 }
514}
515
516fn format_plain_number(value: f64) -> String {
518 let int_val = value as i64;
519 if value == (int_val as f64) {
520 let mut buf = itoa::Buffer::new();
521 buf.format(int_val).to_string()
522 } else {
523 format!("{:.1}", value)
525 }
526}
527
528fn format_with_scale(
534 value: f64,
535 suffixes: &[(char, f64)],
536 i_suffix: &str,
537 round: RoundMethod,
538) -> String {
539 let abs_value = value.abs();
540 let sign = if value < 0.0 { "-" } else { "" };
541
542 let mut chosen_idx: Option<usize> = None;
544
545 for (idx, &(_suffix, mult)) in suffixes.iter().enumerate().rev() {
546 if abs_value >= mult {
547 chosen_idx = Some(idx);
548 break;
549 }
550 }
551
552 let Some(mut idx) = chosen_idx else {
553 return format_plain_number(value);
555 };
556
557 loop {
558 let (suffix, mult) = suffixes[idx];
559 let scaled = value / mult;
560 let abs_scaled = scaled.abs();
561
562 if abs_scaled < 10.0 {
563 let rounded = apply_round_for_display(scaled, round);
565 if rounded.abs() >= 10.0 {
566 let int_val = rounded as i64;
568 if int_val.unsigned_abs() >= 1000 && idx + 1 < suffixes.len() {
569 idx += 1;
570 continue;
571 }
572 let mut itoa_buf = itoa::Buffer::new();
573 let digits = itoa_buf.format(int_val.unsigned_abs());
574 return format!("{sign}{}{}{}", digits, suffix, i_suffix);
575 }
576 return format!("{sign}{:.1}{}{}", rounded.abs(), suffix, i_suffix);
577 } else {
578 let int_val = apply_round_int(scaled, round);
580 if int_val.unsigned_abs() >= 1000 {
581 if idx + 1 < suffixes.len() {
582 idx += 1;
583 continue;
584 }
585 }
587 let mut itoa_buf = itoa::Buffer::new();
588 let digits = itoa_buf.format(int_val.unsigned_abs());
589 return format!("{sign}{}{}{}", digits, suffix, i_suffix);
590 }
591 }
592}
593
594#[inline(always)]
597fn apply_round_for_display(value: f64, method: RoundMethod) -> f64 {
598 let factor = 10.0;
599 let shifted = value * factor;
600 let rounded = match method {
601 RoundMethod::Up => shifted.ceil(),
602 RoundMethod::Down => shifted.floor(),
603 RoundMethod::FromZero => {
604 if shifted >= 0.0 {
605 shifted.ceil()
606 } else {
607 shifted.floor()
608 }
609 }
610 RoundMethod::TowardsZero => {
611 if shifted >= 0.0 {
612 shifted.floor()
613 } else {
614 shifted.ceil()
615 }
616 }
617 RoundMethod::Nearest => shifted.round(),
618 };
619 rounded / factor
620}
621
622#[inline(always)]
624fn apply_round_int(value: f64, method: RoundMethod) -> i64 {
625 match method {
626 RoundMethod::Up => value.ceil() as i64,
627 RoundMethod::Down => value.floor() as i64,
628 RoundMethod::FromZero => {
629 if value >= 0.0 {
630 value.ceil() as i64
631 } else {
632 value.floor() as i64
633 }
634 }
635 RoundMethod::TowardsZero => {
636 if value >= 0.0 {
637 value.floor() as i64
638 } else {
639 value.ceil() as i64
640 }
641 }
642 RoundMethod::Nearest => value.round() as i64,
643 }
644}
645
646fn group_thousands(s: &str) -> String {
648 let (integer_part, rest) = if let Some(dot_pos) = s.find('.') {
650 (&s[..dot_pos], &s[dot_pos..])
651 } else {
652 (s, "")
653 };
654
655 let (sign, digits) = if integer_part.starts_with('-') {
657 ("-", &integer_part[1..])
658 } else {
659 ("", integer_part)
660 };
661
662 if digits.len() <= 3 {
663 return format!("{}{}{}", sign, digits, rest);
664 }
665
666 let mut result = String::with_capacity(digits.len() + digits.len() / 3);
667 let remainder = digits.len() % 3;
668 if remainder > 0 {
669 result.push_str(&digits[..remainder]);
670 }
671 for (i, chunk) in digits.as_bytes()[remainder..].chunks(3).enumerate() {
672 if i > 0 || remainder > 0 {
673 result.push(',');
674 }
675 result.push_str(std::str::from_utf8(chunk).unwrap());
676 }
677
678 format!("{}{}{}", sign, result, rest)
679}
680
681fn apply_format_padding(scaled: &str, fmt: &str) -> String {
684 let bytes = fmt.as_bytes();
685 let mut i = 0;
686
687 while i < bytes.len() && bytes[i] != b'%' {
689 i += 1;
690 }
691 let prefix = &fmt[..i];
692 if i >= bytes.len() {
693 return format!("{}{}", prefix, scaled);
694 }
695 i += 1; let mut left_align = false;
699 while i < bytes.len() {
700 match bytes[i] {
701 b'0' | b'+' | b' ' | b'#' | b'\'' => {}
702 b'-' => left_align = true,
703 _ => break,
704 }
705 i += 1;
706 }
707
708 let mut width: usize = 0;
710 while i < bytes.len() && bytes[i].is_ascii_digit() {
711 width = width
712 .saturating_mul(10)
713 .saturating_add((bytes[i] - b'0') as usize);
714 i += 1;
715 }
716
717 while i < bytes.len() && (bytes[i] == b'.' || bytes[i].is_ascii_digit()) {
719 i += 1;
720 }
721 if i < bytes.len() {
722 i += 1; }
724 let suffix = &fmt[i..];
725
726 let padded = if width > 0 && scaled.len() < width {
727 let pad_len = width - scaled.len();
728 if left_align {
729 format!("{}{}", scaled, " ".repeat(pad_len))
730 } else {
731 format!("{}{}", " ".repeat(pad_len), scaled)
732 }
733 } else {
734 scaled.to_string()
735 };
736
737 format!("{}{}{}", prefix, padded, suffix)
738}
739
740struct ParsedFormat {
742 prefix: String,
743 suffix: String,
744 zero_pad: bool,
745 left_align: bool,
746 plus_sign: bool,
747 space_sign: bool,
748 width: usize,
749 precision: Option<usize>,
750 conv: char,
751 is_percent: bool,
752}
753
754fn parse_format_spec(fmt: &str) -> Result<ParsedFormat, String> {
756 let bytes = fmt.as_bytes();
757 let mut i = 0;
758
759 while i < bytes.len() && bytes[i] != b'%' {
760 i += 1;
761 }
762 let prefix = fmt[..i].to_string();
763 if i >= bytes.len() {
764 return Err(format!("invalid format: '{}'", fmt));
765 }
766 i += 1;
767
768 if i >= bytes.len() {
769 return Err(format!("invalid format: '{}'", fmt));
770 }
771
772 if bytes[i] == b'%' {
773 return Ok(ParsedFormat {
774 prefix,
775 suffix: String::new(),
776 zero_pad: false,
777 left_align: false,
778 plus_sign: false,
779 space_sign: false,
780 width: 0,
781 precision: None,
782 conv: '%',
783 is_percent: true,
784 });
785 }
786
787 let mut zero_pad = false;
788 let mut left_align = false;
789 let mut plus_sign = false;
790 let mut space_sign = false;
791 while i < bytes.len() {
792 match bytes[i] {
793 b'0' => zero_pad = true,
794 b'-' => left_align = true,
795 b'+' => plus_sign = true,
796 b' ' => space_sign = true,
797 b'#' | b'\'' => {}
798 _ => break,
799 }
800 i += 1;
801 }
802
803 let mut width: usize = 0;
804 while i < bytes.len() && bytes[i].is_ascii_digit() {
805 width = width
806 .saturating_mul(10)
807 .saturating_add((bytes[i] - b'0') as usize);
808 i += 1;
809 }
810
811 let mut precision: Option<usize> = None;
812 if i < bytes.len() && bytes[i] == b'.' {
813 i += 1;
814 let mut prec: usize = 0;
815 while i < bytes.len() && bytes[i].is_ascii_digit() {
816 prec = prec
817 .saturating_mul(10)
818 .saturating_add((bytes[i] - b'0') as usize);
819 i += 1;
820 }
821 precision = Some(prec);
822 }
823
824 if i >= bytes.len() {
825 return Err(format!("invalid format: '{}'", fmt));
826 }
827 let conv = bytes[i] as char;
828 i += 1;
829 let suffix = fmt[i..].to_string();
830
831 Ok(ParsedFormat {
832 prefix,
833 suffix,
834 zero_pad,
835 left_align,
836 plus_sign,
837 space_sign,
838 width,
839 precision,
840 conv,
841 is_percent: false,
842 })
843}
844
845fn apply_parsed_format(value: f64, pf: &ParsedFormat) -> Result<String, String> {
847 if pf.is_percent {
848 return Ok(format!("{}%", pf.prefix));
849 }
850
851 let prec = pf.precision.unwrap_or(6);
852 let formatted = match pf.conv {
853 'f' => format!("{:.prec$}", value, prec = prec),
854 'e' => format_scientific(value, prec, 'e'),
855 'E' => format_scientific(value, prec, 'E'),
856 'g' => format_g(value, prec, false),
857 'G' => format_g(value, prec, true),
858 _ => return Err(format!("invalid format character: '{}'", pf.conv)),
859 };
860
861 let sign_str = if value < 0.0 {
862 ""
863 } else if pf.plus_sign {
864 "+"
865 } else if pf.space_sign {
866 " "
867 } else {
868 ""
869 };
870
871 let num_str = if !sign_str.is_empty() && !formatted.starts_with('-') {
872 format!("{}{}", sign_str, formatted)
873 } else {
874 formatted
875 };
876
877 let padded = if pf.width > 0 && num_str.len() < pf.width {
878 let pad_len = pf.width - num_str.len();
879 if pf.left_align {
880 format!("{}{}", num_str, " ".repeat(pad_len))
881 } else if pf.zero_pad {
882 if num_str.starts_with('-') || num_str.starts_with('+') || num_str.starts_with(' ') {
883 let (sign, rest) = num_str.split_at(1);
884 format!("{}{}{}", sign, "0".repeat(pad_len), rest)
885 } else {
886 format!("{}{}", "0".repeat(pad_len), num_str)
887 }
888 } else {
889 format!("{}{}", " ".repeat(pad_len), num_str)
890 }
891 } else {
892 num_str
893 };
894
895 Ok(format!("{}{}{}", pf.prefix, padded, pf.suffix))
896}
897
898fn format_scientific(value: f64, prec: usize, e_char: char) -> String {
900 if value == 0.0 {
901 let sign = if value.is_sign_negative() { "-" } else { "" };
902 if prec == 0 {
903 return format!("{sign}0{e_char}+00");
904 }
905 return format!("{sign}0.{:0>prec$}{e_char}+00", "", prec = prec);
906 }
907
908 let abs = value.abs();
909 let sign = if value < 0.0 { "-" } else { "" };
910 let exp = abs.log10().floor() as i32;
911 let mantissa = abs / 10f64.powi(exp);
912
913 let factor = 10f64.powi(prec as i32);
914 let mantissa = (mantissa * factor).round() / factor;
915
916 let (mantissa, exp) = if mantissa >= 10.0 {
917 (mantissa / 10.0, exp + 1)
918 } else {
919 (mantissa, exp)
920 };
921
922 let exp_sign = if exp >= 0 { '+' } else { '-' };
923 let exp_abs = exp.unsigned_abs();
924
925 if prec == 0 {
926 format!("{sign}{mantissa:.0}{e_char}{exp_sign}{exp_abs:02}")
927 } else {
928 format!(
929 "{sign}{mantissa:.prec$}{e_char}{exp_sign}{exp_abs:02}",
930 prec = prec
931 )
932 }
933}
934
935fn format_g(value: f64, prec: usize, upper: bool) -> String {
937 let prec = if prec == 0 { 1 } else { prec };
938
939 if value == 0.0 {
940 let sign = if value.is_sign_negative() { "-" } else { "" };
941 return format!("{sign}0");
942 }
943
944 let abs = value.abs();
945 let exp = abs.log10().floor() as i32;
946 let e_char = if upper { 'E' } else { 'e' };
947
948 if exp < -4 || exp >= prec as i32 {
949 let sig_prec = prec.saturating_sub(1);
950 let s = format_scientific(value, sig_prec, e_char);
951 trim_g_zeros(&s)
952 } else {
953 let decimal_prec = if prec as i32 > exp + 1 {
954 (prec as i32 - exp - 1) as usize
955 } else {
956 0
957 };
958 let s = format!("{value:.decimal_prec$}");
959 trim_g_zeros(&s)
960 }
961}
962
963fn trim_g_zeros(s: &str) -> String {
964 if let Some(e_pos) = s.find(['e', 'E']) {
965 let (mantissa, exponent) = s.split_at(e_pos);
966 let trimmed = mantissa.trim_end_matches('0').trim_end_matches('.');
967 format!("{trimmed}{exponent}")
968 } else {
969 s.trim_end_matches('0').trim_end_matches('.').to_string()
970 }
971}
972
973fn convert_number(
975 token: &str,
976 config: &NumfmtConfig,
977 parsed_fmt: Option<&ParsedFormat>,
978) -> Result<String, String> {
979 let raw_value = parse_number_with_suffix(token, config.from)?;
981
982 let value = raw_value * config.from_unit;
984
985 let value = value / config.to_unit;
987
988 let mut result = if let Some(pf) = parsed_fmt {
990 if config.to != ScaleUnit::None {
992 let scaled = format_scaled(value, config.to, config.round);
993 apply_format_padding(&scaled, config.format.as_deref().unwrap_or("%f"))
994 } else {
995 let rounded = apply_round(value, config.round);
996 apply_parsed_format(rounded, pf)?
997 }
998 } else if config.to != ScaleUnit::None {
999 format_scaled(value, config.to, config.round)
1000 } else {
1001 let rounded = apply_round(value, config.round);
1002 format_plain_number(rounded)
1003 };
1004
1005 if config.grouping {
1007 result = group_thousands(&result);
1008 }
1009
1010 if let Some(ref suffix) = config.suffix {
1012 result.push_str(suffix);
1013 }
1014
1015 if let Some(pad) = config.padding {
1017 let pad_width = pad.unsigned_abs() as usize;
1018 if result.len() < pad_width {
1019 let deficit = pad_width - result.len();
1020 if pad < 0 {
1021 result = format!("{}{}", result, " ".repeat(deficit));
1023 } else {
1024 result = format!("{}{}", " ".repeat(deficit), result);
1026 }
1027 }
1028 }
1029
1030 Ok(result)
1031}
1032
1033fn convert_number_to_buf(
1037 token: &str,
1038 config: &NumfmtConfig,
1039 parsed_fmt: Option<&ParsedFormat>,
1040 out: &mut Vec<u8>,
1041) -> Result<(), String> {
1042 let raw_value = parse_number_with_suffix(token, config.from)?;
1044
1045 let value = raw_value * config.from_unit / config.to_unit;
1047
1048 let use_fast = parsed_fmt.is_none()
1050 && !config.grouping
1051 && config.suffix.is_none()
1052 && config.padding.is_none();
1053
1054 if use_fast && config.to != ScaleUnit::None {
1055 write_scaled_to_buf(out, value, config.to, config.round);
1056 return Ok(());
1057 }
1058
1059 if use_fast && config.to == ScaleUnit::None {
1060 let rounded = apply_round(value, config.round);
1061 write_plain_number_to_buf(out, rounded);
1062 return Ok(());
1063 }
1064
1065 let result = if let Some(pf) = parsed_fmt {
1067 if config.to != ScaleUnit::None {
1068 let scaled = format_scaled(value, config.to, config.round);
1069 apply_format_padding(&scaled, config.format.as_deref().unwrap_or("%f"))
1070 } else {
1071 let rounded = apply_round(value, config.round);
1072 apply_parsed_format(rounded, pf)?
1073 }
1074 } else if config.to != ScaleUnit::None {
1075 format_scaled(value, config.to, config.round)
1076 } else {
1077 let rounded = apply_round(value, config.round);
1078 format_plain_number(rounded)
1079 };
1080
1081 let mut result = result;
1082
1083 if config.grouping {
1084 result = group_thousands(&result);
1085 }
1086
1087 if let Some(ref suffix) = config.suffix {
1088 result.push_str(suffix);
1089 }
1090
1091 if let Some(pad) = config.padding {
1092 let pad_width = pad.unsigned_abs() as usize;
1093 if result.len() < pad_width {
1094 let deficit = pad_width - result.len();
1095 if pad < 0 {
1096 result = format!("{}{}", result, " ".repeat(deficit));
1097 } else {
1098 result = format!("{}{}", " ".repeat(deficit), result);
1099 }
1100 }
1101 }
1102
1103 out.extend_from_slice(result.as_bytes());
1104 Ok(())
1105}
1106
1107fn split_fields<'a>(line: &'a str, delimiter: Option<char>) -> Vec<&'a str> {
1109 match delimiter {
1110 Some(delim) => line.split(delim).collect(),
1111 None => {
1112 let mut fields = Vec::new();
1115 let bytes = line.as_bytes();
1116 let len = bytes.len();
1117 let mut i = 0;
1118 let mut field_start = 0;
1119 let mut in_space = true;
1120 let mut first = true;
1121
1122 while i < len {
1123 let c = bytes[i];
1124 if c == b' ' || c == b'\t' || c == b'\r' || c == b'\x0b' || c == b'\x0c' {
1125 if !in_space && !first {
1126 fields.push(&line[field_start..i]);
1127 }
1128 in_space = true;
1129 i += 1;
1130 } else {
1131 if in_space {
1132 field_start = i;
1133 in_space = false;
1134 first = false;
1135 }
1136 i += 1;
1137 }
1138 }
1139 if !in_space {
1140 fields.push(&line[field_start..]);
1141 }
1142
1143 if fields.is_empty() {
1144 vec![line]
1145 } else {
1146 fields
1147 }
1148 }
1149 }
1150}
1151
1152fn reassemble_fields(
1154 original: &str,
1155 fields: &[&str],
1156 converted: &[String],
1157 delimiter: Option<char>,
1158) -> String {
1159 match delimiter {
1160 Some(delim) => converted.join(&delim.to_string()),
1161 None => {
1162 let mut result = String::with_capacity(original.len());
1164 let mut field_idx = 0;
1165 let mut in_space = true;
1166 let mut i = 0;
1167 let bytes = original.as_bytes();
1168
1169 while i < bytes.len() {
1170 let c = bytes[i] as char;
1171 if c.is_ascii_whitespace() {
1172 if !in_space && field_idx > 0 {
1173 }
1175 result.push(c);
1176 in_space = true;
1177 i += 1;
1178 } else {
1179 if in_space {
1180 in_space = false;
1181 if field_idx < converted.len() {
1183 result.push_str(&converted[field_idx]);
1184 } else if field_idx < fields.len() {
1185 result.push_str(fields[field_idx]);
1186 }
1187 field_idx += 1;
1188 while i < bytes.len() && !(bytes[i] as char).is_ascii_whitespace() {
1190 i += 1;
1191 }
1192 continue;
1193 }
1194 i += 1;
1195 }
1196 }
1197
1198 result
1199 }
1200 }
1201}
1202
1203pub fn process_line(line: &str, config: &NumfmtConfig) -> Result<String, String> {
1205 process_line_with_fmt(line, config, None)
1206}
1207
1208fn process_line_with_fmt(
1210 line: &str,
1211 config: &NumfmtConfig,
1212 parsed_fmt: Option<&ParsedFormat>,
1213) -> Result<String, String> {
1214 let fields = split_fields(line, config.delimiter);
1215
1216 if fields.is_empty() {
1217 return Ok(line.to_string());
1218 }
1219
1220 let all_fields = config.field.is_empty();
1221
1222 let mut converted: Vec<String> = Vec::with_capacity(fields.len());
1223 for (i, field) in fields.iter().enumerate() {
1224 let field_num = i + 1; let should_convert = all_fields || config.field.contains(&field_num);
1226
1227 if should_convert {
1228 match convert_number(field, config, parsed_fmt) {
1229 Ok(s) => converted.push(s),
1230 Err(e) => match config.invalid {
1231 InvalidMode::Abort => return Err(e),
1232 InvalidMode::Fail => {
1233 eprintln!("numfmt: {}", e);
1234 converted.push(field.to_string());
1235 }
1236 InvalidMode::Warn => {
1237 eprintln!("numfmt: {}", e);
1238 converted.push(field.to_string());
1239 }
1240 InvalidMode::Ignore => {
1241 converted.push(field.to_string());
1242 }
1243 },
1244 }
1245 } else {
1246 converted.push(field.to_string());
1247 }
1248 }
1249
1250 Ok(reassemble_fields(
1251 line,
1252 &fields,
1253 &converted,
1254 config.delimiter,
1255 ))
1256}
1257
1258fn process_line_fast_delim(
1262 line: &[u8],
1263 delim: u8,
1264 field_set: &FieldSet,
1265 config: &NumfmtConfig,
1266 parsed_fmt: Option<&ParsedFormat>,
1267 out: &mut Vec<u8>,
1268) -> Result<(), String> {
1269 let mut field_num: usize = 1;
1270 let mut start = 0;
1271 let len = line.len();
1272
1273 loop {
1274 let end = memchr::memchr(delim, &line[start..])
1276 .map(|pos| start + pos)
1277 .unwrap_or(len);
1278
1279 if field_set.contains(field_num) {
1280 let field_str = std::str::from_utf8(&line[start..end])
1284 .map_err(|_| "invalid number: '<non-utf8>'".to_string())?;
1285
1286 match convert_number_to_buf(field_str, config, parsed_fmt, out) {
1287 Ok(()) => {}
1288 Err(e) => match config.invalid {
1289 InvalidMode::Abort => return Err(e),
1290 InvalidMode::Fail | InvalidMode::Warn => {
1291 eprintln!("numfmt: {}", e);
1292 out.extend_from_slice(&line[start..end]);
1293 }
1294 InvalidMode::Ignore => {
1295 out.extend_from_slice(&line[start..end]);
1296 }
1297 },
1298 }
1299 } else {
1300 out.extend_from_slice(&line[start..end]);
1302 }
1303
1304 if end >= len {
1305 break;
1306 }
1307
1308 out.push(delim);
1310 start = end + 1;
1311 field_num += 1;
1312 }
1313
1314 Ok(())
1315}
1316
1317fn process_line_fast_ws(
1320 line: &[u8],
1321 field_set: &FieldSet,
1322 config: &NumfmtConfig,
1323 parsed_fmt: Option<&ParsedFormat>,
1324 out: &mut Vec<u8>,
1325) -> Result<(), String> {
1326 let len = line.len();
1327 let mut i = 0;
1328 let mut field_num: usize = 0;
1329
1330 while i < len {
1332 let c = line[i];
1333 if c == b' ' || c == b'\t' || c == b'\r' || c == b'\x0b' || c == b'\x0c' {
1334 out.push(c);
1336 i += 1;
1337 } else {
1338 field_num += 1;
1340 let field_start = i;
1341
1342 while i < len {
1344 let fc = line[i];
1345 if fc == b' ' || fc == b'\t' || fc == b'\r' || fc == b'\x0b' || fc == b'\x0c' {
1346 break;
1347 }
1348 i += 1;
1349 }
1350 let field_end = i;
1351
1352 if field_set.contains(field_num) {
1353 let field_str = std::str::from_utf8(&line[field_start..field_end])
1354 .map_err(|_| "invalid number: '<non-utf8>'".to_string())?;
1355
1356 match convert_number_to_buf(field_str, config, parsed_fmt, out) {
1357 Ok(()) => {}
1358 Err(e) => match config.invalid {
1359 InvalidMode::Abort => return Err(e),
1360 InvalidMode::Fail | InvalidMode::Warn => {
1361 eprintln!("numfmt: {}", e);
1362 out.extend_from_slice(&line[field_start..field_end]);
1363 }
1364 InvalidMode::Ignore => {
1365 out.extend_from_slice(&line[field_start..field_end]);
1366 }
1367 },
1368 }
1369 } else {
1370 out.extend_from_slice(&line[field_start..field_end]);
1371 }
1372 }
1373 }
1374
1375 if field_num == 0 {
1378 if field_set.contains(1) {
1379 match convert_number_to_buf("", config, parsed_fmt, out) {
1380 Ok(()) => {}
1381 Err(e) => match config.invalid {
1382 InvalidMode::Abort | InvalidMode::Fail => return Err(e),
1383 InvalidMode::Warn => {
1384 eprintln!("numfmt: {}", e);
1385 }
1386 InvalidMode::Ignore => {}
1387 },
1388 }
1389 } else {
1390 out.extend_from_slice(line);
1391 }
1392 }
1393
1394 Ok(())
1395}
1396
1397pub fn run_numfmt<R: std::io::BufRead, W: Write>(
1399 input: R,
1400 mut output: W,
1401 config: &NumfmtConfig,
1402) -> Result<(), String> {
1403 let parsed_fmt = if let Some(ref fmt) = config.format {
1405 Some(parse_format_spec(fmt)?)
1406 } else {
1407 None
1408 };
1409
1410 let field_set = FieldSet::from_config(&config.field);
1412
1413 let terminator = if config.zero_terminated { b'\0' } else { b'\n' };
1414 let mut header_remaining = config.header;
1415 let mut buf = Vec::with_capacity(4096);
1416 let mut out_buf = Vec::with_capacity(4096);
1417 let mut reader = input;
1418 let mut had_error = false;
1419
1420 let delim_byte = config.delimiter.map(|c| {
1422 if c.is_ascii() { Some(c as u8) } else { None }
1424 });
1425 let delim_byte = delim_byte.and_then(|x| x);
1427
1428 loop {
1429 buf.clear();
1430 let bytes_read = reader
1431 .read_until(terminator, &mut buf)
1432 .map_err(|e| format!("read error: {}", e))?;
1433 if bytes_read == 0 {
1434 break;
1435 }
1436
1437 let line = if buf.last() == Some(&terminator) {
1439 &buf[..buf.len() - 1]
1440 } else {
1441 &buf[..]
1442 };
1443
1444 if header_remaining > 0 {
1445 header_remaining -= 1;
1446 output
1447 .write_all(line)
1448 .map_err(|e| format!("write error: {}", e))?;
1449 output
1450 .write_all(&[terminator])
1451 .map_err(|e| format!("write error: {}", e))?;
1452 continue;
1453 }
1454
1455 out_buf.clear();
1456
1457 let result = if let Some(db) = delim_byte {
1459 process_line_fast_delim(
1460 line,
1461 db,
1462 &field_set,
1463 config,
1464 parsed_fmt.as_ref(),
1465 &mut out_buf,
1466 )
1467 } else if config.delimiter.is_some() {
1468 let line_str = String::from_utf8_lossy(line);
1470 match process_line_with_fmt(&line_str, config, parsed_fmt.as_ref()) {
1471 Ok(result) => {
1472 out_buf.extend_from_slice(result.as_bytes());
1473 Ok(())
1474 }
1475 Err(e) => Err(e),
1476 }
1477 } else {
1478 process_line_fast_ws(line, &field_set, config, parsed_fmt.as_ref(), &mut out_buf)
1480 };
1481
1482 match result {
1483 Ok(()) => {
1484 output
1485 .write_all(&out_buf)
1486 .map_err(|e| format!("write error: {}", e))?;
1487 output
1488 .write_all(&[terminator])
1489 .map_err(|e| format!("write error: {}", e))?;
1490 }
1491 Err(e) => match config.invalid {
1492 InvalidMode::Abort => {
1493 eprintln!("numfmt: {}", e);
1494 return Err(e);
1495 }
1496 InvalidMode::Fail => {
1497 eprintln!("numfmt: {}", e);
1498 output
1499 .write_all(line)
1500 .map_err(|e| format!("write error: {}", e))?;
1501 output
1502 .write_all(&[terminator])
1503 .map_err(|e| format!("write error: {}", e))?;
1504 had_error = true;
1505 }
1506 InvalidMode::Warn => {
1507 eprintln!("numfmt: {}", e);
1508 output
1509 .write_all(line)
1510 .map_err(|e| format!("write error: {}", e))?;
1511 output
1512 .write_all(&[terminator])
1513 .map_err(|e| format!("write error: {}", e))?;
1514 }
1515 InvalidMode::Ignore => {
1516 output
1517 .write_all(line)
1518 .map_err(|e| format!("write error: {}", e))?;
1519 output
1520 .write_all(&[terminator])
1521 .map_err(|e| format!("write error: {}", e))?;
1522 }
1523 },
1524 }
1525 }
1526
1527 output.flush().map_err(|e| format!("flush error: {}", e))?;
1528
1529 if had_error {
1530 Err("conversion errors occurred".to_string())
1531 } else {
1532 Ok(())
1533 }
1534}