jiff/fmt/util.rs
1use crate::{
2 error::{err, ErrorContext},
3 fmt::Parsed,
4 util::{
5 escape, parse,
6 rangeint::RFrom,
7 t::{self, C},
8 },
9 Error, SignedDuration, Span, Unit,
10};
11
12/// A simple formatter for converting `i64` values to ASCII byte strings.
13///
14/// This avoids going through the formatting machinery which seems to
15/// substantially slow things down.
16///
17/// The `itoa` crate does the same thing as this formatter, but is a bit
18/// faster. We roll our own which is a bit slower, but gets us enough of a win
19/// to be satisfied with and with (almost) pure safe code.
20///
21/// By default, this only includes the sign if it's negative. To always include
22/// the sign, set `force_sign` to `true`.
23#[derive(Clone, Copy, Debug)]
24pub(crate) struct DecimalFormatter {
25 force_sign: Option<bool>,
26 minimum_digits: u8,
27 padding_byte: u8,
28}
29
30impl DecimalFormatter {
31 /// Creates a new decimal formatter using the default configuration.
32 pub(crate) const fn new() -> DecimalFormatter {
33 DecimalFormatter {
34 force_sign: None,
35 minimum_digits: 0,
36 padding_byte: b'0',
37 }
38 }
39
40 /// Format the given value using this configuration as a decimal ASCII
41 /// number.
42 #[cfg(test)]
43 pub(crate) const fn format(&self, value: i64) -> Decimal {
44 Decimal::new(self, value)
45 }
46
47 /// Forces the sign to be rendered, even if it's positive.
48 ///
49 /// When `zero_is_positive` is true, then a zero value is formatted with a
50 /// positive sign. Otherwise, it is formatted with a negative sign.
51 #[cfg(test)]
52 pub(crate) const fn force_sign(
53 self,
54 zero_is_positive: bool,
55 ) -> DecimalFormatter {
56 DecimalFormatter { force_sign: Some(zero_is_positive), ..self }
57 }
58
59 /// The minimum number of digits/padding that this number should be
60 /// formatted with. If the number would have fewer digits than this, then
61 /// it is padded out with the padding byte (which is zero by default) until
62 /// the minimum is reached.
63 ///
64 /// The minimum number of digits is capped at the maximum number of digits
65 /// for an i64 value (which is 19).
66 pub(crate) const fn padding(self, mut digits: u8) -> DecimalFormatter {
67 if digits > Decimal::MAX_I64_DIGITS {
68 digits = Decimal::MAX_I64_DIGITS;
69 }
70 DecimalFormatter { minimum_digits: digits, ..self }
71 }
72
73 /// The padding byte to use when `padding` is set.
74 ///
75 /// The default is `0`.
76 pub(crate) const fn padding_byte(self, byte: u8) -> DecimalFormatter {
77 DecimalFormatter { padding_byte: byte, ..self }
78 }
79}
80
81impl Default for DecimalFormatter {
82 fn default() -> DecimalFormatter {
83 DecimalFormatter::new()
84 }
85}
86
87/// A formatted decimal number that can be converted to a sequence of bytes.
88#[derive(Debug)]
89pub(crate) struct Decimal {
90 buf: [u8; Self::MAX_I64_LEN as usize],
91 start: u8,
92 end: u8,
93}
94
95impl Decimal {
96 /// Discovered via `i64::MIN.to_string().len()`.
97 const MAX_I64_LEN: u8 = 20;
98 /// Discovered via `i64::MAX.to_string().len()`.
99 const MAX_I64_DIGITS: u8 = 19;
100
101 /// Using the given formatter, turn the value given into a decimal
102 /// representation using ASCII bytes.
103 #[cfg_attr(feature = "perf-inline", inline(always))]
104 pub(crate) const fn new(
105 formatter: &DecimalFormatter,
106 mut value: i64,
107 ) -> Decimal {
108 // Specialize the common case to generate tighter codegen.
109 if value >= 0 && formatter.force_sign.is_none() {
110 let mut decimal = Decimal {
111 buf: [0; Self::MAX_I64_LEN as usize],
112 start: Self::MAX_I64_LEN,
113 end: Self::MAX_I64_LEN,
114 };
115 loop {
116 decimal.start -= 1;
117
118 let digit = (value % 10) as u8;
119 value /= 10;
120 decimal.buf[decimal.start as usize] = b'0' + digit;
121 if value == 0 {
122 break;
123 }
124 }
125 while decimal.len() < formatter.minimum_digits {
126 decimal.start -= 1;
127 decimal.buf[decimal.start as usize] = formatter.padding_byte;
128 }
129 return decimal;
130 }
131 Decimal::new_cold(formatter, value)
132 }
133
134 #[cold]
135 #[inline(never)]
136 const fn new_cold(formatter: &DecimalFormatter, value: i64) -> Decimal {
137 let sign = value.signum();
138 let Some(mut value) = value.checked_abs() else {
139 let buf = [
140 b'-', b'9', b'2', b'2', b'3', b'3', b'7', b'2', b'0', b'3',
141 b'6', b'8', b'5', b'4', b'7', b'7', b'5', b'8', b'0', b'8',
142 ];
143 return Decimal { buf, start: 0, end: Self::MAX_I64_LEN };
144 };
145 let mut decimal = Decimal {
146 buf: [0; Self::MAX_I64_LEN as usize],
147 start: Self::MAX_I64_LEN,
148 end: Self::MAX_I64_LEN,
149 };
150 loop {
151 decimal.start -= 1;
152
153 let digit = (value % 10) as u8;
154 value /= 10;
155 decimal.buf[decimal.start as usize] = b'0' + digit;
156 if value == 0 {
157 break;
158 }
159 }
160 while decimal.len() < formatter.minimum_digits {
161 decimal.start -= 1;
162 decimal.buf[decimal.start as usize] = formatter.padding_byte;
163 }
164 if sign < 0 {
165 decimal.start -= 1;
166 decimal.buf[decimal.start as usize] = b'-';
167 } else if let Some(zero_is_positive) = formatter.force_sign {
168 let ascii_sign =
169 if sign > 0 || zero_is_positive { b'+' } else { b'-' };
170 decimal.start -= 1;
171 decimal.buf[decimal.start as usize] = ascii_sign;
172 }
173 decimal
174 }
175
176 /// Returns the total number of ASCII bytes (including the sign) that are
177 /// used to represent this decimal number.
178 #[inline]
179 const fn len(&self) -> u8 {
180 self.end - self.start
181 }
182
183 /// Returns the ASCII representation of this decimal as a byte slice.
184 ///
185 /// The slice returned is guaranteed to be valid ASCII.
186 #[inline]
187 pub(crate) fn as_bytes(&self) -> &[u8] {
188 &self.buf[usize::from(self.start)..usize::from(self.end)]
189 }
190
191 /// Returns the ASCII representation of this decimal as a string slice.
192 #[inline]
193 pub(crate) fn as_str(&self) -> &str {
194 // SAFETY: This is safe because all bytes written to `self.buf` are
195 // guaranteed to be ASCII (including in its initial state), and thus,
196 // any subsequence is guaranteed to be valid UTF-8.
197 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
198 }
199}
200
201/// A simple formatter for converting fractional components to ASCII byte
202/// strings.
203///
204/// We only support precision to 9 decimal places, which corresponds to
205/// nanosecond precision as a fractional second component.
206#[derive(Clone, Copy, Debug)]
207pub(crate) struct FractionalFormatter {
208 precision: Option<u8>,
209}
210
211impl FractionalFormatter {
212 /// Creates a new fractional formatter using the given precision settings.
213 pub(crate) const fn new() -> FractionalFormatter {
214 FractionalFormatter { precision: None }
215 }
216
217 /// Format the given value using this configuration as a decimal ASCII
218 /// fractional number.
219 pub(crate) const fn format(&self, value: i64) -> Fractional {
220 Fractional::new(self, value)
221 }
222
223 /// Set the precision.
224 ///
225 /// If the `precision` is greater than `9`, then it is clamped to `9`.
226 ///
227 /// When the precision is not set, then it is automatically determined
228 /// based on the value.
229 pub(crate) const fn precision(
230 self,
231 precision: Option<u8>,
232 ) -> FractionalFormatter {
233 let precision = match precision {
234 None => None,
235 Some(p) if p > 9 => Some(9),
236 Some(p) => Some(p),
237 };
238 FractionalFormatter { precision, ..self }
239 }
240
241 /// Returns true if and only if at least one digit will be written for the
242 /// given value.
243 ///
244 /// This is useful for callers that need to know whether to write
245 /// a decimal separator, e.g., `.`, before the digits.
246 pub(crate) fn will_write_digits(self, value: i64) -> bool {
247 self.precision.map_or_else(|| value != 0, |p| p > 0)
248 }
249
250 /// Returns true if and only if this formatter has an explicit non-zero
251 /// precision setting.
252 ///
253 /// This is useful for determining whether something like `0.000` needs to
254 /// be written in the case of a `precision=Some(3)` setting and a zero
255 /// value.
256 pub(crate) fn has_non_zero_fixed_precision(self) -> bool {
257 self.precision.map_or(false, |p| p > 0)
258 }
259
260 /// Returns true if and only if this formatter has fixed zero precision.
261 /// That is, no matter what is given as input, a fraction is never written.
262 pub(crate) fn has_zero_fixed_precision(self) -> bool {
263 self.precision.map_or(false, |p| p == 0)
264 }
265}
266
267/// A formatted fractional number that can be converted to a sequence of bytes.
268#[derive(Debug)]
269pub(crate) struct Fractional {
270 buf: [u8; Self::MAX_LEN as usize],
271 end: u8,
272}
273
274impl Fractional {
275 /// Since we don't support precision bigger than this.
276 const MAX_LEN: u8 = 9;
277
278 /// Using the given formatter, turn the value given into a fractional
279 /// decimal representation using ASCII bytes.
280 ///
281 /// Note that the fractional number returned *may* expand to an empty
282 /// slice of bytes. This occurs whenever the precision is set to `0`, or
283 /// when the precision is not set and the value is `0`. Any non-zero
284 /// explicitly set precision guarantees that the slice returned is not
285 /// empty.
286 ///
287 /// This panics if the value given isn't in the range `0..=999_999_999`.
288 pub(crate) const fn new(
289 formatter: &FractionalFormatter,
290 mut value: i64,
291 ) -> Fractional {
292 assert!(0 <= value && value <= 999_999_999);
293 let mut fractional = Fractional {
294 buf: [b'0'; Self::MAX_LEN as usize],
295 end: Self::MAX_LEN,
296 };
297 let mut i = 9;
298 loop {
299 i -= 1;
300
301 let digit = (value % 10) as u8;
302 value /= 10;
303 fractional.buf[i] += digit;
304 if value == 0 {
305 break;
306 }
307 }
308 if let Some(precision) = formatter.precision {
309 fractional.end = precision;
310 } else {
311 while fractional.end > 0
312 && fractional.buf[fractional.end as usize - 1] == b'0'
313 {
314 fractional.end -= 1;
315 }
316 }
317 fractional
318 }
319
320 /// Returns the ASCII representation of this fractional number as a byte
321 /// slice. The slice returned may be empty.
322 ///
323 /// The slice returned is guaranteed to be valid ASCII.
324 pub(crate) fn as_bytes(&self) -> &[u8] {
325 &self.buf[..usize::from(self.end)]
326 }
327
328 /// Returns the ASCII representation of this fractional number as a string
329 /// slice. The slice returned may be empty.
330 pub(crate) fn as_str(&self) -> &str {
331 // SAFETY: This is safe because all bytes written to `self.buf` are
332 // guaranteed to be ASCII (including in its initial state), and thus,
333 // any subsequence is guaranteed to be valid UTF-8.
334 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
335 }
336}
337
338/// Parses an optional fractional number from the start of `input`.
339///
340/// If `input` does not begin with a `.` (or a `,`), then this returns `None`
341/// and no input is consumed. Otherwise, up to 9 ASCII digits are parsed after
342/// the decimal separator.
343///
344/// While this is most typically used to parse the fractional component of
345/// second units, it is also used to parse the fractional component of hours or
346/// minutes in ISO 8601 duration parsing, and milliseconds and microseconds in
347/// the "friendly" duration format. The return type in that case is obviously a
348/// misnomer, but the range of possible values is still correct. (That is, the
349/// fractional component of an hour is still limited to 9 decimal places per
350/// the Temporal spec.)
351#[cfg_attr(feature = "perf-inline", inline(always))]
352pub(crate) fn parse_temporal_fraction<'i>(
353 input: &'i [u8],
354) -> Result<Parsed<'i, Option<t::SubsecNanosecond>>, Error> {
355 // TimeFraction :::
356 // TemporalDecimalFraction
357 //
358 // TemporalDecimalFraction :::
359 // TemporalDecimalSeparator DecimalDigit
360 // TemporalDecimalSeparator DecimalDigit DecimalDigit
361 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
362 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
363 // DecimalDigit
364 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
365 // DecimalDigit DecimalDigit
366 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
367 // DecimalDigit DecimalDigit DecimalDigit
368 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
369 // DecimalDigit DecimalDigit DecimalDigit
370 // DecimalDigit
371 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
372 // DecimalDigit DecimalDigit DecimalDigit
373 // DecimalDigit DecimalDigit
374 // TemporalDecimalSeparator DecimalDigit DecimalDigit DecimalDigit
375 // DecimalDigit DecimalDigit DecimalDigit
376 // DecimalDigit DecimalDigit DecimalDigit
377 //
378 // TemporalDecimalSeparator ::: one of
379 // . ,
380 //
381 // DecimalDigit :: one of
382 // 0 1 2 3 4 5 6 7 8 9
383
384 #[inline(never)]
385 fn imp<'i>(
386 mut input: &'i [u8],
387 ) -> Result<Parsed<'i, Option<t::SubsecNanosecond>>, Error> {
388 let mkdigits = parse::slicer(input);
389 while mkdigits(input).len() <= 8
390 && input.first().map_or(false, u8::is_ascii_digit)
391 {
392 input = &input[1..];
393 }
394 let digits = mkdigits(input);
395 if digits.is_empty() {
396 return Err(err!(
397 "found decimal after seconds component, \
398 but did not find any decimal digits after decimal",
399 ));
400 }
401 // I believe this error can never happen, since we know we have no more
402 // than 9 ASCII digits. Any sequence of 9 ASCII digits can be parsed
403 // into an `i64`.
404 let nanoseconds = parse::fraction(digits, 9).map_err(|err| {
405 err!(
406 "failed to parse {digits:?} as fractional component \
407 (up to 9 digits, nanosecond precision): {err}",
408 digits = escape::Bytes(digits),
409 )
410 })?;
411 // I believe this is also impossible to fail, since the maximal
412 // fractional nanosecond is 999_999_999, and which also corresponds
413 // to the maximal expressible number with 9 ASCII digits. So every
414 // possible expressible value here is in range.
415 let nanoseconds =
416 t::SubsecNanosecond::try_new("nanoseconds", nanoseconds).map_err(
417 |err| err!("fractional nanoseconds are not valid: {err}"),
418 )?;
419 Ok(Parsed { value: Some(nanoseconds), input })
420 }
421
422 if input.is_empty() || (input[0] != b'.' && input[0] != b',') {
423 return Ok(Parsed { value: None, input });
424 }
425 imp(&input[1..])
426}
427
428/// This routine returns a span based on the given with fractional time applied
429/// to it.
430///
431/// For example, given a span like `P1dT1.5h`, the `unit` would be
432/// `Unit::Hour`, the `value` would be `1` and the `fraction` would be
433/// `500_000_000`. The span given would just be `1d`. The span returned would
434/// be `P1dT1h30m`.
435///
436/// Note that `fraction` can be a fractional hour, minute, second, millisecond
437/// or microsecond (even though its type suggests its only a fraction of a
438/// second). When milliseconds or microseconds, the given fraction has any
439/// sub-nanosecond precision truncated.
440///
441/// # Errors
442///
443/// This can error if the resulting units would be too large for the limits on
444/// a `span`. This also errors if `unit` is not `Hour`, `Minute`, `Second`,
445/// `Millisecond` or `Microsecond`.
446#[inline(never)]
447pub(crate) fn fractional_time_to_span(
448 unit: Unit,
449 value: t::NoUnits,
450 fraction: t::SubsecNanosecond,
451 mut span: Span,
452) -> Result<Span, Error> {
453 let allowed = matches!(
454 unit,
455 Unit::Hour
456 | Unit::Minute
457 | Unit::Second
458 | Unit::Millisecond
459 | Unit::Microsecond
460 );
461 if !allowed {
462 return Err(err!(
463 "fractional {unit} units are not allowed",
464 unit = unit.singular(),
465 ));
466 }
467 // We switch everything over to nanoseconds and then divy that up as
468 // appropriate. In general, we always create a balanced span, but there
469 // are some cases where we can't. For example, if one serializes a span
470 // with both the maximum number of seconds and the maximum number of
471 // milliseconds, then this just can't be balanced due to the limits on
472 // each of the units. When this kind of span is serialized to a string,
473 // it results in a second value that is actually bigger than the maximum
474 // allowed number of seconds in a span. So here, we have to reverse that
475 // operation and spread the seconds over smaller units. This in turn
476 // creates an unbalanced span. Annoying.
477 //
478 // The above is why we have `if unit_value > MAX { <do adjustments> }` in
479 // the balancing code below. Basically, if we overshoot our limit, we back
480 // out anything over the limit and carry it over to the lesser units. If
481 // our value is truly too big, then the final call to set nanoseconds will
482 // fail.
483 let value = t::NoUnits128::rfrom(value);
484 let fraction = t::NoUnits128::rfrom(fraction);
485 let mut nanos = match unit {
486 Unit::Hour => {
487 (value * t::NANOS_PER_HOUR) + (fraction * t::SECONDS_PER_HOUR)
488 }
489 Unit::Minute => {
490 (value * t::NANOS_PER_MINUTE) + (fraction * t::SECONDS_PER_MINUTE)
491 }
492 Unit::Second => (value * t::NANOS_PER_SECOND) + fraction,
493 Unit::Millisecond => {
494 (value * t::NANOS_PER_MILLI) + (fraction / t::NANOS_PER_MICRO)
495 }
496 Unit::Microsecond => {
497 (value * t::NANOS_PER_MICRO) + (fraction / t::NANOS_PER_MILLI)
498 }
499 // We return an error above if we hit this case.
500 _ => unreachable!("unsupported unit: {unit:?}"),
501 };
502
503 if unit >= Unit::Hour && nanos > C(0) {
504 let mut hours = nanos / t::NANOS_PER_HOUR;
505 nanos %= t::NANOS_PER_HOUR;
506 if hours > t::SpanHours::MAX_SELF {
507 nanos += (hours - t::SpanHours::MAX_SELF) * t::NANOS_PER_HOUR;
508 hours = t::NoUnits128::rfrom(t::SpanHours::MAX_SELF);
509 }
510 // OK because we just checked that our units are in range.
511 span = span.try_hours_ranged(hours).unwrap();
512 }
513 if unit >= Unit::Minute && nanos > C(0) {
514 let mut minutes = nanos / t::NANOS_PER_MINUTE;
515 nanos %= t::NANOS_PER_MINUTE;
516 if minutes > t::SpanMinutes::MAX_SELF {
517 nanos +=
518 (minutes - t::SpanMinutes::MAX_SELF) * t::NANOS_PER_MINUTE;
519 minutes = t::NoUnits128::rfrom(t::SpanMinutes::MAX_SELF);
520 }
521 // OK because we just checked that our units are in range.
522 span = span.try_minutes_ranged(minutes).unwrap();
523 }
524 if unit >= Unit::Second && nanos > C(0) {
525 let mut seconds = nanos / t::NANOS_PER_SECOND;
526 nanos %= t::NANOS_PER_SECOND;
527 if seconds > t::SpanSeconds::MAX_SELF {
528 nanos +=
529 (seconds - t::SpanSeconds::MAX_SELF) * t::NANOS_PER_SECOND;
530 seconds = t::NoUnits128::rfrom(t::SpanSeconds::MAX_SELF);
531 }
532 // OK because we just checked that our units are in range.
533 span = span.try_seconds_ranged(seconds).unwrap();
534 }
535 if unit >= Unit::Millisecond && nanos > C(0) {
536 let mut millis = nanos / t::NANOS_PER_MILLI;
537 nanos %= t::NANOS_PER_MILLI;
538 if millis > t::SpanMilliseconds::MAX_SELF {
539 nanos +=
540 (millis - t::SpanMilliseconds::MAX_SELF) * t::NANOS_PER_MILLI;
541 millis = t::NoUnits128::rfrom(t::SpanMilliseconds::MAX_SELF);
542 }
543 // OK because we just checked that our units are in range.
544 span = span.try_milliseconds_ranged(millis).unwrap();
545 }
546 if unit >= Unit::Microsecond && nanos > C(0) {
547 let mut micros = nanos / t::NANOS_PER_MICRO;
548 nanos %= t::NANOS_PER_MICRO;
549 if micros > t::SpanMicroseconds::MAX_SELF {
550 nanos +=
551 (micros - t::SpanMicroseconds::MAX_SELF) * t::NANOS_PER_MICRO;
552 micros = t::NoUnits128::rfrom(t::SpanMicroseconds::MAX_SELF);
553 }
554 // OK because we just checked that our units are in range.
555 span = span.try_microseconds_ranged(micros).unwrap();
556 }
557 if nanos > C(0) {
558 span = span.try_nanoseconds_ranged(nanos).with_context(|| {
559 err!(
560 "failed to set nanosecond value {nanos} on span \
561 determined from {value}.{fraction}",
562 )
563 })?;
564 }
565
566 Ok(span)
567}
568
569/// Like `fractional_time_to_span`, but just converts the fraction of the given
570/// unit to a signed duration.
571///
572/// Since a signed duration doesn't keep track of individual units, there is
573/// no loss of fidelity between it and ISO 8601 durations like there is for
574/// `Span`.
575///
576/// Note that `fraction` can be a fractional hour, minute, second, millisecond
577/// or microsecond (even though its type suggests its only a fraction of a
578/// second). When milliseconds or microseconds, the given fraction has any
579/// sub-nanosecond precision truncated.
580///
581/// # Errors
582///
583/// This returns an error if `unit` is not `Hour`, `Minute`, `Second`,
584/// `Millisecond` or `Microsecond`.
585#[inline(never)]
586pub(crate) fn fractional_time_to_duration(
587 unit: Unit,
588 fraction: t::SubsecNanosecond,
589) -> Result<SignedDuration, Error> {
590 let fraction = t::NoUnits::rfrom(fraction);
591 let nanos = match unit {
592 Unit::Hour => fraction * t::SECONDS_PER_HOUR,
593 Unit::Minute => fraction * t::SECONDS_PER_MINUTE,
594 Unit::Second => fraction,
595 Unit::Millisecond => fraction / t::NANOS_PER_MICRO,
596 Unit::Microsecond => fraction / t::NANOS_PER_MILLI,
597 unit => {
598 return Err(err!(
599 "fractional {unit} units are not allowed",
600 unit = unit.singular(),
601 ))
602 }
603 };
604 Ok(SignedDuration::from_nanos(nanos.get()))
605}
606
607#[cfg(test)]
608mod tests {
609 use alloc::string::ToString;
610
611 use super::*;
612
613 #[test]
614 fn decimal() {
615 let x = DecimalFormatter::new().format(i64::MIN);
616 assert_eq!(x.as_str(), "-9223372036854775808");
617
618 let x = DecimalFormatter::new().format(i64::MIN + 1);
619 assert_eq!(x.as_str(), "-9223372036854775807");
620
621 let x = DecimalFormatter::new().format(i64::MAX);
622 assert_eq!(x.as_str(), "9223372036854775807");
623
624 let x = DecimalFormatter::new().force_sign(true).format(i64::MAX);
625 assert_eq!(x.as_str(), "+9223372036854775807");
626
627 let x = DecimalFormatter::new().format(0);
628 assert_eq!(x.as_str(), "0");
629
630 let x = DecimalFormatter::new().force_sign(true).format(0);
631 assert_eq!(x.as_str(), "+0");
632
633 let x = DecimalFormatter::new().force_sign(false).format(0);
634 assert_eq!(x.as_str(), "-0");
635
636 let x = DecimalFormatter::new().padding(4).format(0);
637 assert_eq!(x.as_str(), "0000");
638
639 let x = DecimalFormatter::new().padding(4).format(789);
640 assert_eq!(x.as_str(), "0789");
641
642 let x = DecimalFormatter::new().padding(4).format(-789);
643 assert_eq!(x.as_str(), "-0789");
644
645 let x =
646 DecimalFormatter::new().force_sign(true).padding(4).format(789);
647 assert_eq!(x.as_str(), "+0789");
648 }
649
650 #[test]
651 fn fractional_auto() {
652 let f = |n| FractionalFormatter::new().format(n).as_str().to_string();
653
654 assert_eq!(f(0), "");
655 assert_eq!(f(123_000_000), "123");
656 assert_eq!(f(123_456_000), "123456");
657 assert_eq!(f(123_456_789), "123456789");
658 assert_eq!(f(456_789), "000456789");
659 assert_eq!(f(789), "000000789");
660 }
661
662 #[test]
663 fn fractional_precision() {
664 let f = |precision, n| {
665 FractionalFormatter::new()
666 .precision(Some(precision))
667 .format(n)
668 .as_str()
669 .to_string()
670 };
671
672 assert_eq!(f(0, 0), "");
673 assert_eq!(f(1, 0), "0");
674 assert_eq!(f(9, 0), "000000000");
675
676 assert_eq!(f(3, 123_000_000), "123");
677 assert_eq!(f(6, 123_000_000), "123000");
678 assert_eq!(f(9, 123_000_000), "123000000");
679
680 assert_eq!(f(3, 123_456_000), "123");
681 assert_eq!(f(6, 123_456_000), "123456");
682 assert_eq!(f(9, 123_456_000), "123456000");
683
684 assert_eq!(f(3, 123_456_789), "123");
685 assert_eq!(f(6, 123_456_789), "123456");
686 assert_eq!(f(9, 123_456_789), "123456789");
687
688 // We use truncation, no rounding.
689 assert_eq!(f(2, 889_000_000), "88");
690 assert_eq!(f(2, 999_000_000), "99");
691 }
692}