1use crate::data::core::ValueTypes;
63use crate::data::BindingNotification;
64use crate::utilities::number_format::{
65 pattern, NEGATIVE_CURRENCY_FORMATS, NEGATIVE_NUMBER_FORMATS, NEGATIVE_PERCENT_FORMATS, POSITIVE_CURRENCY_FORMATS,
66 POSITIVE_PERCENT_FORMATS,
67};
68use crate::animation::TimeSpan;
69use crate::utilities::{CultureInfo, DateTime, DateTimeOffset, Decimal, NumberFormatInfo};
70use crate::{BoxedValue, DoNothingType, UnsetValueType};
71use std::fmt;
72
73#[derive(Clone, Copy, Debug, PartialEq)]
75pub enum FormatArg<'a> {
76 Null,
78 Str(&'a str),
80 Bool(bool),
82 Char(char),
84 I8(i8),
86 I16(i16),
88 I32(i32),
90 I64(i64),
92 U64(u64),
94 F64(f64),
96 F32(f32),
98 Decimal(Decimal),
100 DateTime(DateTime),
102 DateTimeOffset(DateTimeOffset),
104 TimeSpan(TimeSpan),
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
111pub enum FormatError {
112 InvalidFormat,
115 IndexOutOfRange,
117 InvalidFormatSpecifier,
119}
120
121impl fmt::Display for FormatError {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 f.write_str(match self {
124 FormatError::InvalidFormat => "Input string was not in a correct format.",
125 FormatError::IndexOutOfRange => {
126 "Index (zero based) must be greater than or equal to zero and less than the size of the argument list."
127 }
128 FormatError::InvalidFormatSpecifier => "Format specifier was invalid.",
129 })
130 }
131}
132
133impl std::error::Error for FormatError {}
134
135const INDEX_LIMIT: usize = 1_000_000;
136const WIDTH_LIMIT: usize = 1_000_000;
137
138pub fn format(format: &str, args: &[FormatArg<'_>]) -> Result<String, FormatError> {
141 format_with(format, args, &CultureInfo::invariant_culture())
142}
143
144pub fn format_with(format: &str, args: &[FormatArg<'_>], culture: &CultureInfo) -> Result<String, FormatError> {
148 fn next(bytes: &[u8], pos: &mut usize) -> Result<u8, FormatError> {
149 *pos += 1;
150 bytes.get(*pos).copied().ok_or(FormatError::InvalidFormat)
151 }
152
153 let bytes = format.as_bytes();
154 let len = bytes.len();
155 let mut out = String::with_capacity(len + args.len() * 8);
156 let mut pos = 0;
157 loop {
158 let start = pos;
161 while pos < len && bytes[pos] != b'{' && bytes[pos] != b'}' {
162 pos += 1;
163 }
164 out.push_str(&format[start..pos]);
165 if pos >= len {
166 return Ok(out);
167 }
168
169 let brace = bytes[pos];
170 let mut ch = next(bytes, &mut pos)?;
171 if ch == brace {
172 out.push(brace as char);
173 pos += 1;
174 continue;
175 }
176 if brace != b'{' {
177 return Err(FormatError::InvalidFormat);
178 }
179
180 if !ch.is_ascii_digit() {
182 return Err(FormatError::InvalidFormat);
183 }
184 let mut index = usize::from(ch - b'0');
185 ch = next(bytes, &mut pos)?;
186 let mut width = 0usize;
187 let mut left_justify = false;
188 let mut item_format = "";
189 if ch != b'}' {
190 while ch.is_ascii_digit() && index < INDEX_LIMIT {
191 index = index * 10 + usize::from(ch - b'0');
192 ch = next(bytes, &mut pos)?;
193 }
194 while ch == b' ' {
195 ch = next(bytes, &mut pos)?;
196 }
197
198 if ch == b',' {
200 loop {
201 ch = next(bytes, &mut pos)?;
202 if ch != b' ' {
203 break;
204 }
205 }
206 if ch == b'-' {
207 left_justify = true;
208 ch = next(bytes, &mut pos)?;
209 }
210 if !ch.is_ascii_digit() {
211 return Err(FormatError::InvalidFormat);
212 }
213 width = usize::from(ch - b'0');
214 ch = next(bytes, &mut pos)?;
215 while ch.is_ascii_digit() && width < WIDTH_LIMIT {
216 width = width * 10 + usize::from(ch - b'0');
217 ch = next(bytes, &mut pos)?;
218 }
219 while ch == b' ' {
220 ch = next(bytes, &mut pos)?;
221 }
222 }
223
224 if ch != b'}' {
226 if ch != b':' {
227 return Err(FormatError::InvalidFormat);
228 }
229 let format_start = pos + 1;
230 loop {
231 ch = next(bytes, &mut pos)?;
232 if ch == b'}' {
233 break;
234 }
235 if ch == b'{' {
236 return Err(FormatError::InvalidFormat);
237 }
238 }
239 item_format = &format[format_start..pos];
240 }
241 }
242 pos += 1;
243
244 let arg = args.get(index).ok_or(FormatError::IndexOutOfRange)?;
245 if width == 0 {
246 write_arg(&mut out, arg, item_format, culture)?;
247 } else {
248 let mut text = String::new();
249 write_arg(&mut text, arg, item_format, culture)?;
250 let padding = width.saturating_sub(text.encode_utf16().count());
251 if left_justify {
252 out.push_str(&text);
253 out.extend(std::iter::repeat_n(' ', padding));
254 } else {
255 out.extend(std::iter::repeat_n(' ', padding));
256 out.push_str(&text);
257 }
258 }
259 }
260}
261
262pub fn format_arg(arg: &FormatArg<'_>, format: &str) -> Result<String, FormatError> {
265 format_arg_with(arg, format, &CultureInfo::invariant_culture())
266}
267
268pub fn format_arg_with(arg: &FormatArg<'_>, format: &str, culture: &CultureInfo) -> Result<String, FormatError> {
271 let mut out = String::new();
272 write_arg(&mut out, arg, format, culture)?;
273 Ok(out)
274}
275
276pub fn format_values(format_string: &str, values: &[Option<BoxedValue>]) -> Result<String, FormatError> {
289 format_values_with(format_string, values, &CultureInfo::current_culture())
290}
291
292pub fn format_values_with(
294 format_string: &str,
295 values: &[Option<BoxedValue>],
296 culture: &CultureInfo,
297) -> Result<String, FormatError> {
298 enum Owned {
299 Arg(FormatArg<'static>),
300 Text(String),
301 }
302
303 fn to_owned(value: &Option<BoxedValue>) -> Owned {
304 let Some(value) = value.clone().and_then(ValueTypes::normalize) else {
305 return Owned::Arg(FormatArg::Null);
306 };
307 macro_rules! primitive {
308 ($($ty:ty => |$v:ident| $arg:expr),* $(,)?) => {
309 $(
310 if let Some($v) = value.downcast_ref::<$ty>() {
311 return Owned::Arg($arg);
312 }
313 )*
314 };
315 }
316 primitive!(
317 i32 => |v| FormatArg::I32(*v),
318 f64 => |v| FormatArg::F64(*v),
319 bool => |v| FormatArg::Bool(*v),
320 i64 => |v| FormatArg::I64(*v),
321 f32 => |v| FormatArg::F32(*v),
322 u8 => |v| FormatArg::U64(u64::from(*v)),
323 u16 => |v| FormatArg::U64(u64::from(*v)),
324 u32 => |v| FormatArg::U64(u64::from(*v)),
325 u64 => |v| FormatArg::U64(*v),
326 usize => |v| FormatArg::U64(*v as u64),
327 i8 => |v| FormatArg::I8(*v),
328 i16 => |v| FormatArg::I16(*v),
329 isize => |v| FormatArg::I64(*v as i64),
330 char => |v| FormatArg::Char(*v),
331 Decimal => |v| FormatArg::Decimal(*v),
332 DateTime => |v| FormatArg::DateTime(*v),
333 DateTimeOffset => |v| FormatArg::DateTimeOffset(*v),
334 TimeSpan => |v| FormatArg::TimeSpan(*v),
335 &'static str => |v| FormatArg::Str(v),
336 );
337 if let Some(s) = value.downcast_ref::<String>() {
338 return Owned::Text(s.clone());
339 }
340 if let Some(v) = value.downcast_ref::<UnsetValueType>() {
341 return Owned::Text(v.to_string());
342 }
343 if let Some(v) = value.downcast_ref::<DoNothingType>() {
344 return Owned::Text(v.to_string());
345 }
346 if let Some(v) = value.downcast_ref::<BindingNotification>() {
347 return Owned::Text(v.to_string());
348 }
349 Owned::Text(ValueTypes::to_display_string(Some(&value)))
350 }
351
352 let owned: Vec<Owned> = values.iter().map(to_owned).collect();
353 let args: Vec<FormatArg<'_>> = owned
354 .iter()
355 .map(|o| match o {
356 Owned::Arg(arg) => *arg,
357 Owned::Text(text) => FormatArg::Str(text),
358 })
359 .collect();
360 format_with(format_string, &args, culture)
361}
362
363fn write_arg(out: &mut String, arg: &FormatArg<'_>, format: &str, culture: &CultureInfo) -> Result<(), FormatError> {
364 let numbers = culture.number_format();
365 let info: &NumberFormatInfo = &numbers;
366 match *arg {
367 FormatArg::Null => {}
368 FormatArg::Str(s) => out.push_str(s),
369 FormatArg::Bool(b) => out.push_str(if b { "True" } else { "False" }),
370 FormatArg::Char(c) => out.push(c),
371 FormatArg::I8(v) => format_integer(out, v < 0, u64::from(v.unsigned_abs()), u64::from(v as u8), format, info)?,
372 FormatArg::I16(v) => format_integer(out, v < 0, u64::from(v.unsigned_abs()), u64::from(v as u16), format, info)?,
373 FormatArg::I32(v) => format_integer(out, v < 0, u64::from(v.unsigned_abs()), u64::from(v as u32), format, info)?,
374 FormatArg::I64(v) => format_integer(out, v < 0, v.unsigned_abs(), v as u64, format, info)?,
375 FormatArg::U64(v) => format_integer(out, false, v, v, format, info)?,
376 FormatArg::F64(v) => format_float(out, Float::F64(v), format, info)?,
377 FormatArg::F32(v) => format_float(out, Float::F32(v), format, info)?,
378 FormatArg::Decimal(v) => out.push_str(&v.to_string_with(format, Some(info))?),
379 FormatArg::DateTime(v) => {
381 out.push_str(&v.try_to_string_format(format, culture).map_err(|_| FormatError::InvalidFormat)?)
382 }
383 FormatArg::DateTimeOffset(v) => {
384 out.push_str(&v.try_to_string_format(format, culture).map_err(|_| FormatError::InvalidFormat)?)
385 }
386 FormatArg::TimeSpan(v) => {
387 out.push_str(&v.try_to_string_format(format).map_err(|_| FormatError::InvalidFormat)?)
388 }
389 }
390 Ok(())
391}
392
393struct Number {
400 digits: Vec<u8>,
402 scale: i32,
403 negative: bool,
404 floating: bool,
407}
408
409impl Number {
410 fn from_integer(negative: bool, abs: u64) -> Self {
411 let digits = if abs == 0 { Vec::new() } else { abs.to_string().into_bytes() };
412 let scale = digits.len() as i32;
413 Number { digits, scale, negative, floating: false }
416 }
417
418 fn from_exponent_text(text: &str, negative: bool) -> Self {
420 let (mantissa, exponent) = text.split_once('e').unwrap_or((text, "0"));
421 let exponent: i32 = exponent.parse().unwrap_or(0);
422 let digits: Vec<u8> = mantissa.bytes().filter(u8::is_ascii_digit).collect();
423 let mut number = Number { digits, scale: exponent + 1, negative, floating: true };
424 number.trim();
425 if number.digits.is_empty() {
426 number.scale = 0;
427 }
428 number
429 }
430
431 fn from_fixed_text(text: &str, negative: bool) -> Self {
433 let integer_len = text.find('.').unwrap_or(text.len());
434 let all: Vec<u8> = text.bytes().filter(u8::is_ascii_digit).collect();
435 let leading_zeros = all.iter().take_while(|&&d| d == b'0').count();
436 let digits = all[leading_zeros..].to_vec();
437 let mut number =
438 Number { digits, scale: integer_len as i32 - leading_zeros as i32, negative, floating: true };
439 number.trim();
440 if number.digits.is_empty() {
441 number.scale = 0;
442 }
443 number
444 }
445
446 fn trim(&mut self) {
447 while self.digits.last() == Some(&b'0') {
448 self.digits.pop();
449 }
450 }
451
452 #[inline]
454 fn digit(&self, index: usize) -> char {
455 self.digits.get(index).map_or('0', |&d| d as char)
456 }
457
458 #[inline]
459 fn is_zero(&self) -> bool {
460 self.digits.is_empty()
461 }
462}
463
464fn round_number(number: &mut Number, pos: i64, is_correctly_rounded: bool) {
467 let len = number.digits.len();
468 let mut i = if pos <= 0 { 0 } else { len.min(usize::try_from(pos).unwrap_or(usize::MAX)) };
469 let at_pos = pos >= 0 && i as i64 == pos;
470 let round_up = at_pos && i < len && !is_correctly_rounded && number.digits[i] >= b'5';
471 if round_up {
472 while i > 0 && number.digits[i - 1] == b'9' {
473 i -= 1;
474 }
475 if i > 0 {
476 number.digits[i - 1] += 1;
477 } else {
478 number.scale += 1;
479 if number.digits.is_empty() {
480 number.digits.push(b'1');
481 } else {
482 number.digits[0] = b'1';
483 }
484 i = 1;
485 }
486 } else {
487 while i > 0 && number.digits[i - 1] == b'0' {
488 i -= 1;
489 }
490 }
491 if i == 0 {
492 if !number.floating {
493 number.negative = false;
494 }
495 number.scale = 0;
496 }
497 number.digits.truncate(i);
498}
499
500enum Specifier {
501 Standard(u8, i32),
503 Custom,
504}
505
506fn parse_format_specifier(format: &str) -> Result<Specifier, FormatError> {
507 let bytes = format.as_bytes();
508 let Some(&c) = bytes.first() else {
509 return Ok(Specifier::Standard(b'G', -1));
510 };
511 if c.is_ascii_alphabetic() {
512 let mut n: i32 = 0;
513 let mut i = 1;
514 while i < bytes.len() && bytes[i].is_ascii_digit() {
515 if n >= 100_000_000 {
516 return Err(FormatError::InvalidFormatSpecifier);
517 }
518 n = n * 10 + i32::from(bytes[i] - b'0');
519 i += 1;
520 }
521 if i == bytes.len() || bytes[i] == 0 {
522 return Ok(Specifier::Standard(c, if i == 1 { -1 } else { n }));
523 }
524 }
525 Ok(if c == 0 { Specifier::Standard(b'G', -1) } else { Specifier::Custom })
526}
527
528fn push_zeros(out: &mut String, count: usize) {
529 out.extend(std::iter::repeat_n('0', count));
530}
531
532fn format_integer(
534 out: &mut String,
535 negative: bool,
536 abs: u64,
537 twos: u64,
538 format: &str,
539 info: &NumberFormatInfo,
540) -> Result<(), FormatError> {
541 let (specifier, precision) = match parse_format_specifier(format)? {
542 Specifier::Standard(specifier, precision) => (specifier, precision),
543 Specifier::Custom => {
544 let mut number = Number::from_integer(negative, abs);
545 let format: Vec<char> = format.chars().collect();
546 number_to_string_format(out, &mut number, &format, info);
547 return Ok(());
548 }
549 };
550 let min_digits = usize::try_from(precision).unwrap_or(0);
551 let padded = |out: &mut String, digits: &str| {
552 push_zeros(out, min_digits.saturating_sub(digits.len()));
553 out.push_str(digits);
554 };
555 match specifier.to_ascii_uppercase() {
556 b'G' if precision < 1 => {
557 if negative {
558 out.push_str(info.negative_sign());
559 }
560 out.push_str(&abs.to_string());
561 }
562 b'D' => {
563 if negative {
564 out.push_str(info.negative_sign());
565 }
566 padded(out, &abs.to_string());
567 }
568 b'X' => {
569 let digits = if specifier == b'X' { format!("{twos:X}") } else { format!("{twos:x}") };
570 padded(out, &digits);
571 }
572 b'B' => padded(out, &format!("{twos:b}")),
573 _ => {
574 let mut number = Number::from_integer(negative, abs);
575 number_to_string(out, &mut number, specifier, precision, info)?;
576 }
577 }
578 Ok(())
579}
580
581#[derive(Clone, Copy)]
582enum Float {
583 F64(f64),
584 F32(f32),
585}
586
587impl Float {
588 fn is_nan(self) -> bool {
589 match self {
590 Float::F64(v) => v.is_nan(),
591 Float::F32(v) => v.is_nan(),
592 }
593 }
594
595 fn is_infinite(self) -> bool {
596 match self {
597 Float::F64(v) => v.is_infinite(),
598 Float::F32(v) => v.is_infinite(),
599 }
600 }
601
602 fn is_negative(self) -> bool {
603 match self {
604 Float::F64(v) => v.is_sign_negative(),
605 Float::F32(v) => v.is_sign_negative(),
606 }
607 }
608
609 fn is_zero(self) -> bool {
610 match self {
611 Float::F64(v) => v == 0.0,
612 Float::F32(v) => v == 0.0,
613 }
614 }
615
616 fn custom_format_precision(self) -> i32 {
618 match self {
619 Float::F64(_) => 15,
620 Float::F32(_) => 7,
621 }
622 }
623
624 fn max_round_trip_digits(self) -> i32 {
626 match self {
627 Float::F64(_) => 17,
628 Float::F32(_) => 9,
629 }
630 }
631
632 fn shortest(self) -> Number {
636 if let Float::F64(v) = self {
641 let quirk = match v.abs().to_bits() {
642 0x3E60_0000_0000_0000 => Some("2.980232238769531e-8"), 0x0410_0000_0000_0000 => Some("4.104536801298376e-289"), _ => None,
645 };
646 if let Some(text) = quirk {
647 return Number::from_exponent_text(text, self.is_negative());
648 }
649 }
650 let text = match self {
651 Float::F64(v) => format!("{:e}", v.abs()),
652 Float::F32(v) => format!("{:e}", v.abs()),
653 };
654 let shortest = Number::from_exponent_text(&text, self.is_negative());
655 let mantissa_len = text.find('e').unwrap_or(text.len());
656 let decimals = mantissa_len.saturating_sub(2);
657 let rounded_text = match self {
658 Float::F64(v) => format!("{:.*e}", decimals, v.abs()),
659 Float::F32(v) => format!("{:.*e}", decimals, v.abs()),
660 };
661 if rounded_text == text {
662 return shortest;
663 }
664 let round_trips = match self {
665 Float::F64(v) => rounded_text.parse::<f64>().is_ok_and(|r| r == v.abs()),
666 Float::F32(v) => rounded_text.parse::<f32>().is_ok_and(|r| r == v.abs()),
667 };
668 if round_trips {
669 Number::from_exponent_text(&rounded_text, self.is_negative())
670 } else {
671 shortest
672 }
673 }
674
675 fn significant_digits(self, count: i32) -> Number {
677 let decimals = (count.clamp(1, 800) - 1) as usize;
679 let text = match self {
680 Float::F64(v) => format!("{:.*e}", decimals, v.abs()),
681 Float::F32(v) => format!("{:.*e}", decimals, v.abs()),
682 };
683 Number::from_exponent_text(&text, self.is_negative())
684 }
685
686 fn decimal_digits(self, count: i32) -> Number {
688 let decimals = count.clamp(0, 1100) as usize;
690 let text = match self {
691 Float::F64(v) => format!("{:.*}", decimals, v.abs()),
692 Float::F32(v) => format!("{:.*}", decimals, v.abs()),
693 };
694 Number::from_fixed_text(&text, self.is_negative())
695 }
696}
697
698fn format_float(out: &mut String, value: Float, format: &str, info: &NumberFormatInfo) -> Result<(), FormatError> {
699 if value.is_nan() {
700 out.push_str(info.nan_symbol());
701 return Ok(());
702 }
703 if value.is_infinite() {
704 out.push_str(if value.is_negative() {
705 info.negative_infinity_symbol()
706 } else {
707 info.positive_infinity_symbol()
708 });
709 return Ok(());
710 }
711
712 let (specifier, precision) = match parse_format_specifier(format)? {
713 Specifier::Standard(specifier, precision) => (specifier, precision),
714 Specifier::Custom => {
715 let mut number = if value.is_zero() {
716 Number { digits: Vec::new(), scale: 0, negative: value.is_negative(), floating: true }
717 } else {
718 value.significant_digits(value.custom_format_precision())
719 };
720 let format: Vec<char> = format.chars().collect();
721 number_to_string_format(out, &mut number, &format, info);
722 return Ok(());
723 }
724 };
725
726 enum Digits {
727 Shortest,
728 Significant(i32),
729 Decimals(i32),
730 }
731 let or_default = |default: i32| if precision == -1 { default } else { precision };
732 let digits = match specifier.to_ascii_uppercase() {
733 b'C' => Digits::Decimals(or_default(info.currency_decimal_digits())),
734 b'F' | b'N' => Digits::Decimals(or_default(info.number_decimal_digits())),
735 b'E' => Digits::Significant(or_default(6).saturating_add(1)),
736 b'G' if precision < 1 => Digits::Shortest,
737 b'G' => Digits::Significant(precision),
738 b'P' => Digits::Decimals(or_default(info.percent_decimal_digits()).saturating_add(2)),
739 b'R' => Digits::Shortest,
740 _ => return Err(FormatError::InvalidFormatSpecifier),
741 };
742
743 let mut max_digits = precision;
744 let mut number = if value.is_zero() {
745 Number { digits: Vec::new(), scale: 0, negative: value.is_negative(), floating: true }
746 } else {
747 match digits {
748 Digits::Shortest => value.shortest(),
749 Digits::Significant(count) => value.significant_digits(count),
750 Digits::Decimals(count) => value.decimal_digits(count),
751 }
752 };
753 if matches!(digits, Digits::Shortest) {
754 max_digits = (number.digits.len() as i32).max(value.max_round_trip_digits());
756 }
757 number_to_string(out, &mut number, specifier, max_digits, info)
758}
759
760const PER_MILLE_SYMBOL: char = '\u{2030}';
765
766fn write_pattern(
770 out: &mut String,
771 pattern: &str,
772 info: &NumberFormatInfo,
773 symbol: &str,
774 mut digits: impl FnMut(&mut String),
775) {
776 for ch in pattern.chars() {
777 match ch {
778 '#' => digits(out),
779 '-' => out.push_str(info.negative_sign()),
780 '$' | '%' => out.push_str(symbol),
781 other => out.push(other),
782 }
783 }
784}
785
786fn number_to_string(
787 out: &mut String,
788 number: &mut Number,
789 format: u8,
790 max_digits: i32,
791 info: &NumberFormatInfo,
792) -> Result<(), FormatError> {
793 let correctly_rounded = number.floating;
794 let mut max_digits = max_digits;
795 match format {
796 b'C' | b'c' => {
797 if max_digits < 0 {
798 max_digits = info.currency_decimal_digits();
799 }
800 round_number(number, i64::from(number.scale) + i64::from(max_digits), correctly_rounded);
801 let layout = if number.negative {
802 pattern(&NEGATIVE_CURRENCY_FORMATS, info.currency_negative_pattern())
803 } else {
804 pattern(&POSITIVE_CURRENCY_FORMATS, info.currency_positive_pattern())
805 };
806 write_pattern(out, layout, info, info.currency_symbol(), |out| {
807 format_fixed(
808 out,
809 number,
810 max_digits,
811 Some(info.currency_group_sizes()),
812 info.currency_decimal_separator(),
813 info.currency_group_separator(),
814 )
815 });
816 }
817 b'F' | b'f' => {
818 if max_digits < 0 {
819 max_digits = info.number_decimal_digits();
820 }
821 round_number(number, i64::from(number.scale) + i64::from(max_digits), correctly_rounded);
822 if number.negative {
823 out.push_str(info.negative_sign());
824 }
825 format_fixed(out, number, max_digits, None, info.number_decimal_separator(), "");
826 }
827 b'N' | b'n' => {
828 if max_digits < 0 {
829 max_digits = info.number_decimal_digits();
830 }
831 round_number(number, i64::from(number.scale) + i64::from(max_digits), correctly_rounded);
832 let layout =
833 if number.negative { pattern(&NEGATIVE_NUMBER_FORMATS, info.number_negative_pattern()) } else { "#" };
834 write_pattern(out, layout, info, "", |out| {
835 format_fixed(
836 out,
837 number,
838 max_digits,
839 Some(info.number_group_sizes()),
840 info.number_decimal_separator(),
841 info.number_group_separator(),
842 )
843 });
844 }
845 b'E' | b'e' => {
846 if max_digits < 0 {
847 max_digits = 6;
848 }
849 max_digits = max_digits.saturating_add(1);
850 round_number(number, i64::from(max_digits), correctly_rounded);
851 if number.negative {
852 out.push_str(info.negative_sign());
853 }
854 format_scientific(out, number, max_digits, format as char, info);
855 }
856 b'G' | b'g' | b'R' | b'r' => {
857 if max_digits < 1 {
858 max_digits = number.digits.len() as i32;
859 }
860 round_number(number, i64::from(max_digits), correctly_rounded);
861 if number.negative {
862 out.push_str(info.negative_sign());
863 }
864 let exponent_char = if format.is_ascii_uppercase() { 'E' } else { 'e' };
865 format_general(out, number, max_digits, exponent_char, info);
866 }
867 b'P' | b'p' => {
868 if max_digits < 0 {
869 max_digits = info.percent_decimal_digits();
870 }
871 number.scale += 2;
872 round_number(number, i64::from(number.scale) + i64::from(max_digits), correctly_rounded);
873 let layout = if number.negative {
874 pattern(&NEGATIVE_PERCENT_FORMATS, info.percent_negative_pattern())
875 } else {
876 pattern(&POSITIVE_PERCENT_FORMATS, info.percent_positive_pattern())
877 };
878 write_pattern(out, layout, info, info.percent_symbol(), |out| {
879 format_fixed(
880 out,
881 number,
882 max_digits,
883 Some(info.percent_group_sizes()),
884 info.percent_decimal_separator(),
885 info.percent_group_separator(),
886 )
887 });
888 }
889 _ => return Err(FormatError::InvalidFormatSpecifier),
890 }
891 Ok(())
892}
893
894fn group_positions(digits: i32, sizes: &[i32]) -> Vec<i32> {
898 let mut positions = Vec::new();
899 let Some(&first) = sizes.first() else { return positions };
900 let mut index = 0;
901 let mut total = first;
902 while digits > total {
903 if sizes[index] == 0 {
904 break;
905 }
906 positions.push(total);
907 if index + 1 < sizes.len() {
908 index += 1;
909 }
910 if sizes[index] == 0 {
911 break;
912 }
913 total += sizes[index];
914 }
915 positions
916}
917
918fn format_fixed(
919 out: &mut String,
920 number: &Number,
921 max_digits: i32,
922 group_sizes: Option<&[i32]>,
923 decimal_separator: &str,
924 group_separator: &str,
925) {
926 let mut cursor = 0usize;
927 let mut dig_pos = number.scale;
928 if dig_pos > 0 {
929 let positions = group_sizes.map(|sizes| group_positions(dig_pos, sizes)).unwrap_or_default();
930 let count = dig_pos as usize;
931 for i in 0..count {
932 out.push(number.digit(cursor));
933 cursor += 1;
934 let remaining = (count - i - 1) as i32;
935 if remaining > 0 && positions.contains(&remaining) {
936 out.push_str(group_separator);
937 }
938 }
939 dig_pos = 0;
940 } else {
941 out.push('0');
942 }
943
944 if max_digits > 0 {
945 let mut remaining = max_digits;
946 out.push_str(decimal_separator);
947 if dig_pos < 0 {
948 let zeros = (-dig_pos).min(remaining);
949 push_zeros(out, zeros as usize);
950 remaining -= zeros;
951 }
952 while remaining > 0 {
953 out.push(number.digit(cursor));
954 cursor += 1;
955 remaining -= 1;
956 }
957 }
958}
959
960fn format_scientific(out: &mut String, number: &Number, max_digits: i32, exponent_char: char, info: &NumberFormatInfo) {
961 out.push(number.digit(0));
962 if max_digits != 1 {
963 out.push_str(info.number_decimal_separator());
965 }
966 for i in 1..max_digits.max(1) as usize {
967 out.push(number.digit(i));
968 }
969 let exponent = if number.is_zero() { 0 } else { number.scale - 1 };
970 format_exponent(out, exponent, exponent_char, 3, true, info);
971}
972
973fn format_exponent(
974 out: &mut String,
975 value: i32,
976 exponent_char: char,
977 min_digits: usize,
978 positive_sign: bool,
979 info: &NumberFormatInfo,
980) {
981 out.push(exponent_char);
982 if value < 0 {
983 out.push_str(info.negative_sign());
984 } else if positive_sign {
985 out.push_str(info.positive_sign());
986 }
987 let digits = value.unsigned_abs().to_string();
988 push_zeros(out, min_digits.saturating_sub(digits.len()));
989 out.push_str(&digits);
990}
991
992fn format_general(out: &mut String, number: &Number, max_digits: i32, exponent_char: char, info: &NumberFormatInfo) {
993 let mut dig_pos = number.scale;
994 let mut scientific = false;
995 if dig_pos > max_digits || dig_pos < -3 {
996 dig_pos = 1;
997 scientific = true;
998 }
999
1000 let mut cursor = 0usize;
1001 if dig_pos > 0 {
1002 while dig_pos > 0 {
1003 out.push(number.digit(cursor));
1004 cursor += 1;
1005 dig_pos -= 1;
1006 }
1007 } else {
1008 out.push('0');
1009 }
1010
1011 if cursor < number.digits.len() || dig_pos < 0 {
1012 out.push_str(info.number_decimal_separator());
1013 while dig_pos < 0 {
1014 out.push('0');
1015 dig_pos += 1;
1016 }
1017 while cursor < number.digits.len() {
1018 out.push(number.digit(cursor));
1019 cursor += 1;
1020 }
1021 }
1022
1023 if scientific {
1024 format_exponent(out, number.scale - 1, exponent_char, 2, true, info);
1025 }
1026}
1027
1028fn find_section(format: &[char], section: i32) -> usize {
1035 if section == 0 {
1036 return 0;
1037 }
1038 let mut section = section;
1039 let mut src = 0;
1040 loop {
1041 if src >= format.len() {
1042 return 0;
1043 }
1044 let ch = format[src];
1045 src += 1;
1046 match ch {
1047 '\'' | '"' => {
1048 while src < format.len() && format[src] != '\0' {
1049 let c = format[src];
1050 src += 1;
1051 if c == ch {
1052 break;
1053 }
1054 }
1055 }
1056 '\\' => {
1057 if src < format.len() && format[src] != '\0' {
1058 src += 1;
1059 }
1060 }
1061 ';' => {
1062 section -= 1;
1063 if section != 0 {
1064 continue;
1065 }
1066 if src < format.len() && format[src] != '\0' && format[src] != ';' {
1067 return src;
1068 }
1069 return 0;
1070 }
1071 '\0' => return 0,
1072 _ => {}
1073 }
1074 }
1075}
1076
1077fn number_to_string_format(out: &mut String, number: &mut Number, format: &[char], info: &NumberFormatInfo) {
1078 let len = format.len();
1079 let at = |index: usize| format.get(index).copied().unwrap_or('\0');
1080
1081 let mut section = find_section(format, if number.is_zero() { 2 } else if number.negative { 1 } else { 0 });
1082 let mut digit_count: i32;
1083 let mut decimal_pos: i32;
1084 let mut first_digit: i32;
1085 let mut last_digit: i32;
1086 let mut scientific: bool;
1087 let mut thousand_pos: i32;
1088 let mut thousand_count: i32 = 0;
1089 let mut thousand_seps: bool;
1090 let mut scale_adjust: i32;
1091
1092 loop {
1093 digit_count = 0;
1094 decimal_pos = -1;
1095 first_digit = i32::MAX;
1096 last_digit = 0;
1097 scientific = false;
1098 thousand_pos = -1;
1099 thousand_seps = false;
1100 scale_adjust = 0;
1101 let mut src = section;
1102
1103 while src < len {
1104 let ch = format[src];
1105 src += 1;
1106 if ch == '\0' || ch == ';' {
1107 break;
1108 }
1109 match ch {
1110 '#' => digit_count += 1,
1111 '0' => {
1112 if first_digit == i32::MAX {
1113 first_digit = digit_count;
1114 }
1115 digit_count += 1;
1116 last_digit = digit_count;
1117 }
1118 '.' => {
1119 if decimal_pos < 0 {
1120 decimal_pos = digit_count;
1121 }
1122 }
1123 ',' => {
1124 if digit_count > 0 && decimal_pos < 0 {
1125 if thousand_pos >= 0 {
1126 if thousand_pos == digit_count {
1127 thousand_count += 1;
1128 continue;
1129 }
1130 thousand_seps = true;
1131 }
1132 thousand_pos = digit_count;
1133 thousand_count = 1;
1134 }
1135 }
1136 '%' => scale_adjust += 2,
1137 PER_MILLE_SYMBOL => scale_adjust += 3,
1138 '\'' | '"' => {
1139 while src < len && format[src] != '\0' {
1140 let c = format[src];
1141 src += 1;
1142 if c == ch {
1143 break;
1144 }
1145 }
1146 }
1147 '\\' => {
1148 if src < len && format[src] != '\0' {
1149 src += 1;
1150 }
1151 }
1152 'E' | 'e' => {
1153 if at(src) == '0' || ((at(src) == '+' || at(src) == '-') && at(src + 1) == '0') {
1154 src += 1;
1155 while src < len && format[src] == '0' {
1156 src += 1;
1157 }
1158 scientific = true;
1159 }
1160 }
1161 _ => {}
1162 }
1163 }
1164
1165 if decimal_pos < 0 {
1166 decimal_pos = digit_count;
1167 }
1168
1169 if thousand_pos >= 0 {
1170 if thousand_pos == decimal_pos {
1171 scale_adjust -= thousand_count * 3;
1172 } else {
1173 thousand_seps = true;
1174 }
1175 }
1176
1177 if !number.is_zero() {
1178 number.scale += scale_adjust;
1179 let pos = if scientific {
1180 i64::from(digit_count)
1181 } else {
1182 i64::from(number.scale) + i64::from(digit_count) - i64::from(decimal_pos)
1183 };
1184 round_number(number, pos, false);
1185 if number.is_zero() {
1186 let zero_section = find_section(format, 2);
1187 if zero_section != section {
1188 section = zero_section;
1189 continue;
1190 }
1191 }
1192 } else {
1193 if !number.floating {
1194 number.negative = false;
1196 }
1197 number.scale = 0;
1198 }
1199 break;
1200 }
1201
1202 let first_digit = if first_digit < decimal_pos { decimal_pos - first_digit } else { 0 };
1203 let last_digit = if last_digit > decimal_pos { decimal_pos - last_digit } else { 0 };
1204 let mut dig_pos: i32;
1205 let mut adjust: i32;
1206 if scientific {
1207 dig_pos = decimal_pos;
1208 adjust = 0;
1209 } else {
1210 dig_pos = number.scale.max(decimal_pos);
1211 adjust = number.scale - decimal_pos;
1212 }
1213
1214 let mut separator_positions: Vec<i32> = Vec::new();
1216 if thousand_seps {
1217 let total_digits = dig_pos + adjust.min(0);
1218 let num_digits = first_digit.max(total_digits);
1219 separator_positions = group_positions(num_digits, info.number_group_sizes());
1220 }
1221
1222 let start = out.len();
1225 if number.negative && section == 0 && number.scale != 0 {
1226 out.push_str(info.negative_sign());
1227 }
1228
1229 let mut decimal_written = false;
1230 let mut cursor = 0usize;
1231 let mut src = section;
1232 let digits_len = number.digits.len();
1233
1234 let mut write_separator = |out: &mut String, dig_pos: i32| {
1235 if thousand_seps && dig_pos > 1 {
1236 if let Some(&last) = separator_positions.last() {
1237 if dig_pos == last + 1 {
1238 out.push_str(info.number_group_separator());
1239 separator_positions.pop();
1240 }
1241 }
1242 }
1243 };
1244
1245 while src < len {
1246 let ch = format[src];
1247 src += 1;
1248 if ch == '\0' || ch == ';' {
1249 break;
1250 }
1251
1252 if adjust > 0 && matches!(ch, '#' | '0' | '.') {
1253 while adjust > 0 {
1255 out.push(number.digit(cursor));
1256 cursor += 1;
1257 write_separator(out, dig_pos);
1258 dig_pos -= 1;
1259 adjust -= 1;
1260 }
1261 }
1262
1263 match ch {
1264 '#' | '0' => {
1265 let digit = if adjust < 0 {
1266 adjust += 1;
1267 if dig_pos <= first_digit { Some('0') } else { None }
1268 } else if cursor < digits_len {
1269 cursor += 1;
1270 Some(number.digit(cursor - 1))
1271 } else if dig_pos > last_digit {
1272 Some('0')
1273 } else {
1274 None
1275 };
1276 if let Some(digit) = digit {
1277 out.push(digit);
1278 write_separator(out, dig_pos);
1279 }
1280 dig_pos -= 1;
1281 }
1282 '.' => {
1283 if dig_pos == 0
1285 && !decimal_written
1286 && (last_digit < 0 || (decimal_pos < digit_count && cursor < digits_len))
1287 {
1288 out.push_str(info.number_decimal_separator());
1289 decimal_written = true;
1290 }
1291 }
1292 PER_MILLE_SYMBOL => out.push_str(info.per_mille_symbol()),
1293 '%' => out.push_str(info.percent_symbol()),
1294 ',' => {}
1295 '\'' | '"' => {
1296 while src < len && format[src] != '\0' && format[src] != ch {
1297 out.push(format[src]);
1298 src += 1;
1299 }
1300 if src < len && format[src] != '\0' {
1301 src += 1;
1302 }
1303 }
1304 '\\' => {
1305 if src < len && format[src] != '\0' {
1306 out.push(format[src]);
1307 src += 1;
1308 }
1309 }
1310 'E' | 'e' => {
1311 if scientific {
1312 let mut positive_sign = false;
1313 let mut min_digits = 0usize;
1314 if at(src) == '0' {
1315 min_digits += 1;
1317 } else if at(src) == '+' && at(src + 1) == '0' {
1318 positive_sign = true;
1319 } else if at(src) == '-' && at(src + 1) == '0' {
1320 } else {
1322 out.push(ch);
1323 continue;
1324 }
1325 src += 1;
1326 while src < len && format[src] == '0' {
1327 min_digits += 1;
1328 src += 1;
1329 }
1330 let exponent = if number.is_zero() { 0 } else { number.scale - decimal_pos };
1331 format_exponent(out, exponent, ch, min_digits.min(10), positive_sign, info);
1332 scientific = false;
1333 } else {
1334 out.push(ch);
1335 if src < len {
1336 if format[src] == '+' || format[src] == '-' {
1337 out.push(format[src]);
1338 src += 1;
1339 }
1340 while src < len && format[src] == '0' {
1341 out.push(format[src]);
1342 src += 1;
1343 }
1344 }
1345 }
1346 }
1347 _ => out.push(ch),
1348 }
1349 }
1350
1351 if number.negative && section == 0 && number.scale == 0 && out.len() > start {
1352 out.insert_str(start, info.negative_sign());
1353 }
1354}