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