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}