Skip to main content

malachite_float/float/conversion/string/
format_float.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MPFR Library.
4//
5//      Copyright © 1999-2024 Free Software Foundation, Inc.
6//
7//      Contributed by the AriC and Caramba projects, INRIA.
8//
9// This file is part of Malachite.
10//
11// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
12// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
13// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
14
15// Faithful port of the floating-point (`%R...`) formatting path of MPFR's `mpfr_vasprintf`
16// (`vasprintf.c`, MPFR 4.2.2): conversion-specification parsing plus the `sprnt_fp` /
17// `partition_number` machinery, built on top of `Float::get_str`. The C `char *format` cursor is
18// rendered as a `&[u8]` slice that the parser functions advance by returning the unconsumed tail.
19//
20// `format_float_str` (below) is the public MPFR-compatible entry point, for callers who want strict
21// `mpfr_printf`-style formatting of a single `Float`. Everything else is internal: `format` /
22// `PrintfArg` are the multi-conversion engine it delegates to, and `format_float` /
23// `float_conversion_spec` / `PrintfSpec` are the spec-based core; all are exposed only under
24// `test_build`, for tests/conversion/string/format_float.rs.
25//
26// Porting status (2026-07-17):
27// - DONE — the ENTIRE `%R` float path works, and `format_float_str` is public. It is validated
28//   against MPFR (via rug's `get_str` oracle) in tests/conversion/string/format_float.rs, for all
29//   of 'e'/'f'/'g' [base 10] and 'a'/'A'/'b' [bases 16/2]; printf has no other float bases. Chain:
30//   spec/flag/arg-type parsing, buffer ops (on a plain `Vec<u8>`), `NumberParts`/`DecimalInfo`,
31//   `mpfr_get_str_wrapper`, `floor_log10` (on `Float::unsigned_pow`), `number_parts_init`,
32//   `regular_eg` (scientific), `regular_fg` (fixed), `next_base_power_p` + `regular_ab`
33//   (hex/binary), `partition_number` (dispatcher), `sprnt_fp` (emitter), `format_float` (a
34//   per-conversion entry point), `format` (a format-string frontend over a `&[PrintfArg]` slice),
35//   and `format_float_str` (the public single-value entry point).
36// - REMAINING: the multi-argument `format` frontend is not yet public (its `PrintfArg` model would
37//   need finalizing); `Display` is a separate `get_str`-based implementation (to_sci.rs), not built
38//   on this. Note the `'` flag divergence below for any future full-string FFI oracle.
39// - Deliberate divergences from MPFR:
40//   - Malachite zeros are precision-less (unlike MPFR), so `%e`-of-zero with an empty precision
41//     falls back to precision 1.
42//   - The `'` flag always groups with a comma; MPFR uses the locale's separator, which is EMPTY in
43//     the default C locale (where MPFR therefore prints no separators).
44//   - A width or precision literal that overflows an `i64` makes `format` return `None` (MPFR sets
45//     EOVERFLOW and returns -1), as does any conversion the `PrintfArg` model cannot supply and any
46//     internal size overflow (MPFR's -1 returns).
47//   - `Exact` rounding is supported (not an MPFR mode): it panics whenever the output does not
48//     represent the value exactly, consistent with `get_str`.
49//   - MPFR 4.2.2's single-digit rounding bug is FIXED here (with an exactness check): MPFR rounds
50//     exact values away under away-rounding modes ("%.0RUa" of 1.5 gives 0xdp-3 = 1.625) and
51//     overflows its digit table when the top digit is 0xf ("%.0RUa" of 15 prints garbage), and it
52//     misses inexactness below the top significand limb ("%.0RUb" of 2^100 + 1 is not rounded up).
53
54use crate::Float;
55use crate::InnerFloat::{Finite, Infinity, NaN, Zero};
56use crate::float::conversion::string::get_str::{ceil_mul, get_str, get_str_ndigits};
57use alloc::format;
58use alloc::string::String;
59use alloc::vec;
60use alloc::vec::Vec;
61use core::cmp::Ordering::{Equal, Greater, Less};
62use malachite_base::fail_on_untested_path;
63use malachite_base::num::basic::integers::PrimitiveInt;
64use malachite_base::num::basic::traits::{One, OneHalf};
65use malachite_base::num::comparison::traits::PartialOrdAbs;
66use malachite_base::num::conversion::string::to_string::digit_to_display_byte_lower;
67use malachite_base::num::conversion::traits::ExactFrom;
68use malachite_base::num::logic::traits::{BitAccess, LowMask, SignificantBits};
69use malachite_base::rounding_modes::RoundingMode::{
70    self, Ceiling, Down, Exact, Floor, Nearest, Up,
71};
72use malachite_nz::natural::Natural;
73use malachite_nz::platform::Limb;
74
75// All the types described by the `type` field of the format string.
76//
77// This is `enum arg_t` from `vasprintf.c`, MPFR 4.2.2, without its `UNSUPPORTED` variant: MPFR
78// assigns it only in `#ifndef` fallbacks for C types its build lacks (`intmax_t`, `long long`,
79// `long double`, `ptrdiff_t`), which always exist here, so this port never constructs it —
80// unsupported conversions are instead rejected by `specinfo_is_valid` or by `format` itself.
81#[derive(Clone, Copy, Eq, PartialEq)]
82pub(crate) enum ArgType {
83    None,
84    Char,
85    Short,
86    Long,
87    LongLong,
88    IntMax,
89    Size,
90    PtrDiff,
91    LongDouble,
92    Mpf,
93    Mpq,
94    MpLimb,
95    MpLimbArray,
96    Mpz,
97    MpfrPrec,
98    Mpfr,
99}
100
101// A single conversion specification of the format string, filled in by the parser. (Adapted, like
102// the MPFR original, from the GNU libc structure.) `width` and `prec` use `i64` for MPFR's
103// `mpfr_intmax_t`; `prec` is negative when omitted. `spec` and `pad` are single bytes, holding the
104// conversion specifier and the padding character.
105//
106// This is `struct printf_spec` from `vasprintf.c`, MPFR 4.2.2. (Its `size` field, 0 iff snprintf
107// with size = 0, is not ported: the count-only mode it selected was dropped.) The struct is `pub`
108// under `test_build` so the tests can name it (they build values via `float_conversion_spec` and
109// never touch the fields, which stay `pub(crate)`).
110pub_crate_test_struct! {
111#[derive(Clone, Copy)]
112PrintfSpec {
113    pub(crate) alt: bool,      // `#` flag
114    pub(crate) space: bool,    // space flag
115    pub(crate) left: bool,     // `-` flag
116    pub(crate) showsign: bool, // `+` flag
117    pub(crate) group: bool,    // `'` flag
118    pub(crate) width: i64,
119    pub(crate) prec: i64,
120    pub(crate) arg_type: ArgType,
121    pub(crate) rnd_mode: RoundingMode,
122    pub(crate) spec: u8,
123    pub(crate) pad: u8,
124}}
125
126// This is `specinfo_init` from `vasprintf.c`, MPFR 4.2.2.
127const fn specinfo_init() -> PrintfSpec {
128    PrintfSpec {
129        alt: false,
130        space: false,
131        left: false,
132        showsign: false,
133        group: false,
134        width: 0,
135        prec: 0,
136        arg_type: ArgType::None,
137        rnd_mode: Nearest,
138        spec: b'\0',
139        pad: b' ',
140    }
141}
142
143// Note: LONG_ARG is unusual, but is accepted (ISO C99 says "has no effect on a following a, A, e,
144// E, f, F, g, or G conversion specifier").
145//
146// This is `FLOATING_POINT_ARG_TYPE` from `vasprintf.c`, MPFR 4.2.2.
147const fn floating_point_arg_type(at: ArgType) -> bool {
148    matches!(
149        at,
150        ArgType::Mpfr | ArgType::Mpf | ArgType::Long | ArgType::LongDouble
151    )
152}
153
154// This is `INTEGER_LIKE_ARG_TYPE` from `vasprintf.c`, MPFR 4.2.2.
155const fn integer_like_arg_type(at: ArgType) -> bool {
156    matches!(
157        at,
158        ArgType::Short
159            | ArgType::Long
160            | ArgType::LongLong
161            | ArgType::IntMax
162            | ArgType::MpfrPrec
163            | ArgType::Mpz
164            | ArgType::Mpq
165            | ArgType::MpLimb
166            | ArgType::MpLimbArray
167            | ArgType::Char
168            | ArgType::Size
169            | ArgType::PtrDiff
170    )
171}
172
173// Whether `spec` is a valid (supported) conversion. (MPFR's version returns a third state for `n`,
174// which it rejects with an error rather than merely considering invalid; this port drops `%n` like
175// any other invalid conversion, so a `bool` suffices.)
176//
177// This is `specinfo_is_valid` from `vasprintf.c`, MPFR 4.2.2.
178fn specinfo_is_valid(spec: PrintfSpec) -> bool {
179    match spec.spec {
180        // 'F': see below
181        b'a' | b'A' | b'e' | b'E' | b'f' | b'g' | b'G' => {
182            spec.arg_type == ArgType::None || floating_point_arg_type(spec.arg_type)
183        }
184        // 'F' only supports MPFR_ARG, since GMP doesn't support it (it is the mpf_t specifier); 'b'
185        // is MPFR-specific.
186        b'F' | b'b' => spec.arg_type == ArgType::Mpfr,
187        b'd' | b'i' | b'o' | b'u' | b'x' | b'X' => {
188            spec.arg_type == ArgType::None || integer_like_arg_type(spec.arg_type)
189        }
190        b'c' | b's' => matches!(spec.arg_type, ArgType::None | ArgType::Long),
191        b'p' => spec.arg_type == ArgType::None,
192        _ => false,
193    }
194}
195
196// Consumes the leading flag characters of `format`, recording them in `specinfo`, and returns the
197// unconsumed tail.
198//
199// This is `parse_flags` from `vasprintf.c`, MPFR 4.2.2.
200fn parse_flags<'a>(mut format: &'a [u8], specinfo: &mut PrintfSpec) -> &'a [u8] {
201    while let Some(&c) = format.first() {
202        match c {
203            b'0' => {
204                specinfo.pad = b'0';
205            }
206            b'#' => {
207                specinfo.alt = true;
208            }
209            b'+' => {
210                specinfo.showsign = true;
211            }
212            b' ' => {
213                specinfo.space = true;
214            }
215            b'-' => {
216                specinfo.left = true;
217            }
218            // Single UNIX Specification for thousand separator
219            b'\'' => {
220                specinfo.group = true;
221            }
222            _ => {
223                return format;
224            }
225        }
226        format = &format[1..];
227    }
228    format
229}
230
231// Consumes the length-modifier / type prefix of `format`, recording the argument type in
232// `specinfo`, and returns the unconsumed tail. `HAVE_LONG_LONG` and the `intmax_t` support are
233// assumed present (always true on the platforms Malachite targets).
234//
235// This is `parse_arg_type` from `vasprintf.c`, MPFR 4.2.2.
236const fn parse_arg_type<'a>(format: &'a [u8], specinfo: &mut PrintfSpec) -> &'a [u8] {
237    let Some((&format_head, mut format_tail)) = format.split_first() else {
238        return format;
239    };
240    specinfo.arg_type = match format_head {
241        b'h' => {
242            if let Some((b'h', tail)) = format_tail.split_first() {
243                format_tail = tail;
244                ArgType::Char
245            } else {
246                ArgType::Short
247            }
248        }
249        b'l' => {
250            if let Some((b'l', tail)) = format_tail.split_first() {
251                format_tail = tail;
252                ArgType::LongLong
253            } else {
254                ArgType::Long
255            }
256        }
257        b'j' => ArgType::IntMax,
258        b'z' => ArgType::Size,
259        b't' => ArgType::PtrDiff,
260        b'L' => ArgType::LongDouble,
261        b'F' => ArgType::Mpf,
262        b'Q' => ArgType::Mpq,
263        // The 'M' specifier was added in GMP 4.2.0.
264        b'M' => ArgType::MpLimb,
265        b'N' => ArgType::MpLimbArray,
266        b'Z' => ArgType::Mpz,
267        // mpfr-specific specifiers
268        b'P' => ArgType::MpfrPrec,
269        b'R' => ArgType::Mpfr,
270        // not a length modifier — leave it for the conversion parser
271        _ => return format,
272    };
273    format_tail
274}
275
276// The output buffer is a plain `Vec<u8>`, which subsumes MPFR's `struct string_buffer` and its
277// `buffer_init` / `buffer_widen` / `buffer_incr_len` machinery (a manually-`realloc`-ed C buffer
278// plus a `len` that becomes -1 on overflow, for the snprintf return value): growth is automatic,
279// and a length exceeding `usize::MAX` is not a reachable state. `buffer_cat` is
280// `extend_from_slice`. The size-0 (count-only `snprintf`) mode is likewise dropped: this engine
281// always produces the full output.
282
283// Adds `n` copies of the character `c` to the end of the buffer `b` (a no-op when `n` is 0).
284//
285// This is `buffer_pad` from `vasprintf.c`, MPFR 4.2.2.
286fn buffer_pad(b: &mut Vec<u8>, c: u8, n: i64) {
287    let new_len = b.len() + usize::exact_from(n);
288    b.resize(new_len, c);
289}
290
291// Concatenates the digits `str` and `tz` trailing zero(s) to the buffer `b`, inserting the
292// character `c` every 3 characters from end to beginning. `c` must not be null and `tz` must be 0
293// or 1.
294//
295// This is `buffer_sandwich` from `vasprintf.c`, MPFR 4.2.2.
296fn buffer_sandwich(b: &mut Vec<u8>, mut str: &[u8], tz: usize, c: u8) {
297    const STEP: usize = 3;
298    assert!(tz == 0 || tz == 1);
299    assert!(c != b'\0');
300    let size = str.len() + tz; // number of digits
301    assert!(size > 0);
302    let q = (size - 1) / STEP; // number of separators c
303    let r = ((size - 1) % STEP) + 1; // number of digits in the leftmost block
304    // first r significant digits (leftmost block)
305    if r <= str.len() {
306        b.extend_from_slice(&str[..r]);
307        str = &str[r..];
308    } else {
309        // r > str.len(), and as a consequence: str.len() < STEP, size <= STEP, q == 0, r == size,
310        // and tz == 1
311        b.extend_from_slice(str);
312        b.push(b'0'); // trailing zero
313    }
314    for _ in 0..q {
315        b.push(c);
316        if str.len() >= STEP {
317            b.extend_from_slice(&str[..STEP]);
318            str = &str[STEP..];
319        } else {
320            // last digits (i == q - 1 and STEP - str.len() == 1)
321            b.extend_from_slice(str);
322            b.push(b'0'); // trailing zero
323        }
324    }
325}
326
327// MPFR's `string_list` / `init_string_list` / `clear_string_list` / `register_string` (a manual
328// list for freeing the temporary digit strings produced while formatting) are not ported: in safe
329// Rust those temporaries are owned `Vec`s/`String`s freed by their scope, so no registry is needed.
330
331// Where the padding characters go.
332//
333// This is `enum pad_t` from `vasprintf.c`, MPFR 4.2.2.
334enum PadType {
335    Left,         // spaces on the left, for right justification
336    LeadingZeros, // '0' padding in the integral part
337    Right,        // spaces on the right, for left justification
338}
339
340// Details how many characters are needed in each part of a float printout. MPFR's `ip_ptr` and
341// `fp_ptr` point into the single `mpfr_get_str` digit string (sometimes both into the same string,
342// sometimes a static "0"/"1"), with a `string_list` owning the allocation; that aliasing can't be
343// expressed with safe references, so each digit-bearing part is an owned `Vec<u8>` here (the
344// shared-allocation optimization is dropped) and MPFR's `*_size` fields collapse into the vectors'
345// lengths. The fixed base prefix ("0x", "0X", "0b", "0B") is a `&'static [u8]`.
346//
347// This is `struct number_parts` from `vasprintf.c`, MPFR 4.2.2.
348struct NumberParts {
349    pad_type: PadType,
350    pad_size: i64,
351    sign: u8,                // sign character: '-', '+', ' ', or '\0'
352    prefix: &'static [u8],   // prefix part (was prefix_ptr / prefix_size)
353    thousands_sep: u8,       // thousands separator (only with style 'f'); '\0' if none
354    ip: Vec<u8>,             // integral-part digits (was ip_ptr / ip_size)
355    ip_trailing_digits: i32, // additional integral zeros (from rounding up to a power of 10)
356    point: u8,               // decimal point character, or '\0'
357    fp_leading_zeros: i64,   // additional leading zeros in the fractional part
358    fp: Vec<u8>,             // fractional-part digits (was fp_ptr / fp_size)
359    fp_trailing_zeros: i64,  // additional trailing zeros in the fractional part
360    exp: Vec<u8>,            // exponent part (was exp_ptr / exp_size)
361}
362
363// Returns `s` with its trailing '0' characters removed. (This inlines the strip-trailing-zeros
364// loops that `vasprintf.c` repeats in `regular_eg`, `regular_fg`, and `regular_ab`; `to_sci` uses
365// it too.)
366pub(crate) fn strip_trailing_zeros(mut s: &[u8]) -> &[u8] {
367    while let [rest @ .., b'0'] = s {
368        s = rest;
369    }
370    s
371}
372
373// `get_str` returns the digits of a negative number with a leading '-'; skips it, so that `str` is
374// pure digits.
375fn skip_sign<'a>(str: &'a [u8], p: &Float) -> &'a [u8] {
376    if p.is_sign_negative() { &str[1..] } else { str }
377}
378
379// The number of significant digits to request from `get_str` for the precision `prec`: 0 (letting
380// `get_str` decide) when the precision is unset, and otherwise one digit before the point plus
381// `prec` after it. `None` if that overflows a `usize`.
382fn nsd_for_prec(prec: i64) -> Option<usize> {
383    if prec < 0 {
384        Some(0)
385    } else {
386        let nsd = usize::try_from(prec).ok().and_then(|p| p.checked_add(1));
387        if nsd.is_none() {
388            fail_on_untested_path("nsd_for_prec, nsd overflows usize");
389        }
390        nsd
391    }
392}
393
394// The exponent part of the output: the marker letter ('e'/'E' for the decimal conversions, 'p'/'P'
395// for hexadecimal/binary), the exponent's sign, and its decimal digits, zero-padded to at least
396// `min_digits` (2 for the decimal conversions, which show at least two digits; 1 otherwise).
397fn exponent_part(marker: u8, exp: i64, min_digits: usize) -> Vec<u8> {
398    let mut out = vec![marker, if exp >= 0 { b'+' } else { b'-' }];
399    out.extend_from_slice(format!("{:0min_digits$}", exp.unsigned_abs()).as_bytes());
400    out
401}
402
403// Records the result of a `get_str` call so that this expensive function is not called more than
404// once for the same number.
405//
406// This is `struct decimal_info` from `vasprintf.c`, MPFR 4.2.2.
407struct DecimalInfo {
408    exp: i64,
409    str: Vec<u8>,
410}
411
412// Returns the base-`base` digits of `op` with `n` significant digits, rounded according to
413// `spec.rnd_mode`, together with the base-`base` exponent. (MPFR's size-0 `snprintf` fast path,
414// which estimates the printed length from a few significant digits, is not ported: this engine
415// always produces the full output. Since `get_str` panics under `Exact` rounding as soon as the
416// result is known to be inexact, that behavior carries over for free.)
417//
418// This is `mpfr_get_str_wrapper` from `vasprintf.c`, MPFR 4.2.2.
419fn mpfr_get_str_wrapper(base: i64, n: usize, op: &Float, spec: &PrintfSpec) -> (Vec<u8>, i64) {
420    // base is 2, 10, or 16 -- all valid -- so get_str never returns None.
421    let (s, exp, _) = get_str(op, base, n, spec.rnd_mode).unwrap();
422    (s, exp)
423}
424
425// For a real nonzero number `x`, returns the exponent `f` so that `10^f <= |x| < 10^(f + 1)`.
426//
427// This is `floor_log10` from `vasprintf.c`, MPFR 4.2.2.
428fn floor_log10(x: &Float) -> i64 {
429    // `y` needs enough precision to represent the exponent exactly and to compare with `x`.
430    let prec = x.get_prec().unwrap().max(i64::BITS.into());
431    let exp = ceil_mul(i64::from(x.get_exponent().unwrap()), 10, 1) - 1;
432    // `y = 10 ^ exp`, rounded up. This is fast: `exp` is an integer (not too large), so the
433    // exponentiation reduces to `pow_z` internally.
434    let y = Float::power_of_10_of_float_prec_round(Float::from(exp), prec, Up).0;
435    if x.lt_abs(&y) { exp - 1 } else { exp }
436}
437
438// Initializes a `NumberParts` with the neutral values that `partition_number` sets before filling
439// in the parts specific to a number.
440const fn number_parts_init() -> NumberParts {
441    NumberParts {
442        pad_type: PadType::Right,
443        pad_size: 0,
444        sign: b'\0',
445        prefix: b"",
446        thousands_sep: b'\0',
447        ip: Vec::new(),
448        ip_trailing_digits: 0,
449        point: b'\0',
450        fp_leading_zeros: 0,
451        fp: Vec::new(),
452        fp_trailing_zeros: 0,
453        exp: Vec::new(),
454    }
455}
456
457// Determines the parts of the string representation of the regular number `p` when `spec.spec` is
458// 'e', 'E', 'g', or 'G'. Returns `None` in case of overflow on the sizes.
459//
460// This is `regular_eg` from `vasprintf.c`, MPFR 4.2.2.
461fn regular_eg(
462    np: &mut NumberParts,
463    p: &Float,
464    spec: &PrintfSpec,
465    dec_info: Option<&DecimalInfo>,
466    keep_trailing_zeros: bool,
467) -> Option<()> {
468    let uppercase = matches!(spec.spec, b'E' | b'G');
469    // integral part: one significant digit
470    let storage;
471    let (str, exp): (&[u8], i64) = match dec_info {
472        None => {
473            // We keep the trailing zeros, so `mpfr_get_str_wrapper` may be used.
474            debug_assert!(keep_trailing_zeros);
475            storage = mpfr_get_str_wrapper(10, nsd_for_prec(spec.prec)?, p, spec);
476            (&storage.0, storage.1)
477        }
478        Some(d) => (&d.str, d.exp),
479    };
480    let digits = skip_sign(str, p);
481    np.ip = vec![digits[0]];
482
483    if spec.prec != 0 {
484        // the sign and the first digit have been skipped
485        let mut frac = &digits[1..];
486        if !keep_trailing_zeros {
487            frac = strip_trailing_zeros(frac);
488        }
489        let str_len = frac.len();
490        if str_len != 0 {
491            np.fp = frac.to_vec();
492            debug_assert!(spec.prec < 0 || i64::exact_from(str_len) <= spec.prec);
493            if keep_trailing_zeros && spec.prec > 0 && i64::exact_from(str_len) < spec.prec {
494                // add missing trailing zeros
495                np.fp_trailing_zeros = spec.prec - i64::exact_from(str_len);
496            }
497        }
498    }
499
500    // decimal point
501    if !np.fp.is_empty() || spec.alt {
502        np.point = b'.';
503    }
504
505    // `exp` is the exponent for the decimal point BEFORE the first digit; we want it AFTER the
506    // first digit. No possible overflow because exp < EXP(p) / 3.
507    let exp = exp - 1;
508    np.exp = exponent_part(if uppercase { b'E' } else { b'e' }, exp, 2);
509    Some(())
510}
511
512// Determines the parts of the string representation of the regular number `p` when `spec.spec` is
513// 'f', 'F', 'g', or 'G'. `dec_info` is the previously-computed exponent and string, or `None`.
514// Returns `None` in case of overflow on the sizes.
515//
516// This is `regular_fg` from `vasprintf.c`, MPFR 4.2.2.
517fn regular_fg(
518    np: &mut NumberParts,
519    p: &Float,
520    spec: &PrintfSpec,
521    dec_info: Option<&DecimalInfo>,
522    keep_trailing_zeros: bool,
523) -> Option<()> {
524    // An empty precision field is forbidden here (it means 6, set before the call).
525    debug_assert!(spec.prec >= 0);
526    if p.get_exponent().unwrap() <= 0 {
527        // 0 < |p| < 1; the integral part is usually 0.
528        np.ip = vec![b'0'];
529        if spec.prec == 0 {
530            // The output is "1" or "0", and 0 < |p| < 1 means either is inexact; this branch
531            // bypasses `get_str`, so it must reject `Exact` itself to stay consistent with the
532            // `get_str`-backed paths.
533            assert!(
534                spec.rnd_mode != Exact,
535                "regular_fg: Exact rounding was requested, but {p} is not exactly representable \
536                with 0 fractional digits",
537            );
538            // either 1 or 0
539            let round_up = match spec.rnd_mode {
540                Floor => p.is_sign_negative(),
541                Ceiling => p.is_sign_positive(),
542                Up => true,
543                // note that 0.5 rounds to 0 with Nearest (round ties to even)
544                Nearest => p.partial_cmp_abs(&Float::ONE_HALF).unwrap() == Greater,
545                _ => false,
546            };
547            if round_up {
548                np.ip[0] = b'1';
549            }
550        } else {
551            // exp = position of the most significant decimal digit
552            let exp = floor_log10(p);
553            debug_assert!(exp < 0);
554            if exp < -spec.prec {
555                // Only the last digit may be nonzero, and exp < -spec.prec means the printed value
556                // (0 or 10^-prec in absolute value) is never |p| itself, so this
557                // `get_str`-bypassing branch is always inexact and must reject `Exact`.
558                let round_away = match spec.rnd_mode {
559                    Up => true,
560                    Down => false,
561                    Floor => p.is_sign_negative(),
562                    Ceiling => p.is_sign_positive(),
563                    Exact => panic!(
564                        "regular_fg: Exact rounding was requested, but {p} is not exactly \
565                        representable with {} fractional digits",
566                        spec.prec
567                    ),
568                    Nearest => {
569                        // compare |p| to y = 0.5 * 10^(-spec.prec), increasing the precision of y
570                        // until it differs from |p| so that the comparison is decisive
571                        let mut e = p.get_prec().unwrap().max(56);
572                        loop {
573                            e += 8;
574                            let y = Float::power_of_10_of_float_prec_round(
575                                Float::from(-spec.prec),
576                                e,
577                                Down,
578                            )
579                            .0 >> 1u64;
580                            let cmp = y.partial_cmp_abs(p).unwrap();
581                            if cmp != Equal {
582                                break cmp == Less;
583                            }
584                        }
585                    }
586                };
587                np.fp_leading_zeros = if round_away {
588                    // the last output digit is '1'
589                    np.fp = vec![b'1'];
590                    spec.prec - 1
591                } else {
592                    // only zeros in the fractional part
593                    debug_assert!(spec.spec == b'f' || spec.spec == b'F');
594                    spec.prec
595                };
596            } else {
597                // exp >= -spec.prec: the significant digits are the last spec.prec + exp + 1 digits
598                // in the fractional part
599                let storage;
600                let (str, exp): (&[u8], i64) = match dec_info {
601                    None => {
602                        debug_assert!(keep_trailing_zeros);
603                        // no overflow: exp <= -1, so the sum is at most spec.prec
604                        debug_assert!(exp <= -1 && spec.prec + (exp + 1) >= 0);
605                        let Ok(nsd) = usize::try_from(spec.prec + (exp + 1)) else {
606                            fail_on_untested_path("regular_fg, sub-1 nsd overflows usize");
607                            return None;
608                        };
609                        storage = mpfr_get_str_wrapper(10, nsd, p, spec);
610                        (&storage.0, storage.1)
611                    }
612                    Some(d) => (&d.str, d.exp),
613                };
614                let digits = skip_sign(str, p);
615                if exp == 1 {
616                    // rounded up to 1
617                    debug_assert!(digits[0] == b'1');
618                    np.ip[0] = b'1';
619                    if keep_trailing_zeros {
620                        np.fp_leading_zeros = spec.prec;
621                    }
622                } else {
623                    np.fp_leading_zeros = -exp;
624                    debug_assert!(exp <= 0);
625                    let digits = if keep_trailing_zeros {
626                        digits
627                    } else {
628                        strip_trailing_zeros(digits)
629                    };
630                    let str_len = digits.len();
631                    debug_assert!(str_len > 0);
632                    np.fp = digits.to_vec();
633                    if keep_trailing_zeros {
634                        // add missing trailing zeros so that fp_size + fp_trailing_zeros equals
635                        // prec + exp
636                        np.fp_trailing_zeros = (spec.prec + exp) - i64::exact_from(str_len);
637                        debug_assert!(np.fp_trailing_zeros >= 0);
638                    }
639                }
640            }
641        }
642        if spec.alt || np.fp_leading_zeros != 0 || !np.fp.is_empty() || np.fp_trailing_zeros != 0 {
643            np.point = b'.';
644        }
645    } else {
646        // 1 <= |p|
647        let storage;
648        let (str, exp): (&[u8], i64) = match dec_info {
649            None => {
650                // %f case. (The %g case has no use for `floor_log10`, whose power of 10 is computed
651                // at the full precision of `p`, so it is only called here.)
652                let exp = floor_log10(p);
653                debug_assert!(exp >= 0);
654                // MPFR computes this sum in `mpfr_uintmax_t` so that it cannot overflow the signed
655                // type; use a checked addition instead.
656                let n = usize::try_from(spec.prec.checked_add(exp + 1)?).ok()?;
657                storage = mpfr_get_str_wrapper(10, n, p, spec);
658                (&storage.0, storage.1)
659            }
660            // %g case
661            Some(d) => (&d.str, d.exp),
662        };
663        let digits = skip_sign(str, p);
664        let str_len = digits.len();
665        // integral part: `exp` (from get_str) is the number of integral digits
666        let ip_size = if exp > i64::exact_from(str_len) {
667            // rounding up to the next power of 10 requires an added trailing zero
668            np.ip_trailing_digits = i32::exact_from(exp - i64::exact_from(str_len));
669            str_len
670        } else {
671            usize::exact_from(exp)
672        };
673        np.ip = digits[..ip_size].to_vec();
674        if spec.group {
675            // MPFR uses the locale's thousands separator here, which is EMPTY in the default C
676            // locale (so MPFR prints no separators there); Malachite has no locale machinery, so
677            // the `'` flag always groups with a comma.
678            np.thousands_sep = b',';
679        }
680        // fractional part
681        let mut frac = &digits[ip_size..];
682        if !keep_trailing_zeros {
683            frac = strip_trailing_zeros(frac);
684        }
685        let frac_len = frac.len();
686        if frac_len > 0 {
687            np.point = b'.';
688            np.fp = frac.to_vec();
689        }
690        if keep_trailing_zeros && i64::exact_from(frac_len) < spec.prec {
691            // add missing trailing zeros
692            np.point = b'.';
693            np.fp_trailing_zeros = spec.prec - i64::exact_from(np.fp.len());
694            debug_assert!(np.fp_trailing_zeros >= 0);
695        }
696        if spec.alt {
697            np.point = b'.';
698        }
699    }
700    Some(())
701}
702
703// The default precision for the 'f'/'F'/'g'/'G' conversions (as in C, this is 6).
704const DEFAULT_DECIMAL_PREC: i64 = 6;
705
706// Whether the rounding mode `rnd` rounds a value with sign `neg` away from zero.
707//
708// This is `MPFR_IS_LIKE_RNDA` from MPFR 4.2.2.
709fn is_like_rnda(rnd: RoundingMode, neg: bool) -> bool {
710    rnd == Up || (rnd == Ceiling && !neg) || (rnd == Floor && neg)
711}
712
713// Whether the significand `sig` (normalized, so its top bit is set and its bit count is a multiple
714// of `Limb::WIDTH`) has any set bit below its top `nbits` bits — that is, whether printing the
715// corresponding [`Float`] with a single base-2^`nbits` digit is inexact.
716fn one_digit_is_inexact(sig: &Natural, nbits: u64) -> bool {
717    sig.trailing_zeros().unwrap() < sig.significant_bits() - nbits
718}
719
720// For a real nonzero `x` rounded to a single base-`base` digit, returns whether `x` rounds up to
721// the next power of `base`. `base` is 2 or 16.
722//
723// This is `next_base_power_p` from `vasprintf.c`, MPFR 4.2.2, with a fix for an upstream bug: for
724// the non-`Nearest` rounding modes, MPFR only examines the most significant limb for remaining
725// bits, so inexactness held entirely in the lower limbs is missed and the value is not rounded up
726// (e.g. 2^100 + 1 with "%.0RUb").
727fn next_base_power_p(x: &Float, base: i64, rnd: RoundingMode) -> bool {
728    // the decimal point is after the first digit in this representation
729    let nbits: u64 = if base == 2 { 1 } else { 4 };
730    if rnd == Down
731        || (rnd == Floor && x.is_sign_positive())
732        || (rnd == Ceiling && x.is_sign_negative())
733        || x.get_prec().unwrap() <= nbits
734    {
735        // no rounding when printing x with a single digit
736        return false;
737    }
738    let sig = x.significand_ref().unwrap();
739    let xm = sig.limbs().next_back().unwrap();
740    // mask of the low (WIDTH - nbits) bits
741    let low_mask = Limb::low_mask(Limb::WIDTH - nbits);
742    let high_mask = !low_mask;
743    if (xm & high_mask) ^ high_mask != 0 {
744        // don't round up if some of the first nbits bits are 0
745        return false;
746    }
747    if rnd == Nearest {
748        // round up if the rounding bit is 1
749        xm.get_bit(Limb::WIDTH - nbits - 1)
750    } else {
751        // an away-from-zero-like rounding mode: round up if any remaining bit is 1
752        one_digit_is_inexact(sig, nbits)
753    }
754}
755
756// Determines the parts of the string representation of the regular number `p` when `spec.spec` is
757// 'a', 'A', or 'b'. Returns `None` in case of overflow on the sizes.
758//
759// This is `regular_ab` from `vasprintf.c`, MPFR 4.2.2.
760fn regular_ab(np: &mut NumberParts, p: &Float, spec: &PrintfSpec) -> Option<()> {
761    let uppercase = spec.spec == b'A';
762    if matches!(spec.spec, b'a' | b'A') {
763        np.prefix = if uppercase { b"0X" } else { b"0x" };
764    }
765    let base: i64 = if spec.spec == b'b' { 2 } else { 16 };
766    // the sign-skipped digit string, and the base-two exponent for a point after the first digit
767    let (mut digits, exp): (Vec<u8>, i64) = if spec.prec != 0 {
768        let (s, e) = mpfr_get_str_wrapper(base, nsd_for_prec(spec.prec)?, p, spec);
769        let digits = if p.is_sign_negative() {
770            s[1..].to_vec()
771        } else {
772            s
773        };
774        // base 16: get_str's exponent is base-16 with the point before the first digit; we want
775        // base-2 with the point after the first digit
776        let exp = if base == 16 { (e - 1) << 2 } else { e - 1 };
777        (digits, exp)
778    } else {
779        let mut e = i64::from(p.get_exponent().unwrap());
780        let sig = p.significand_ref().unwrap();
781        // A single digit that drops set bits is inexact; this path bypasses `get_str`, so it must
782        // reject `Exact` itself.
783        assert!(
784            spec.rnd_mode != Exact || !one_digit_is_inexact(sig, if base == 2 { 1 } else { 4 }),
785            "regular_ab: Exact rounding was requested, but {p} is not exactly representable with \
786            a single base-{base} digit",
787        );
788        let digit_byte = if next_base_power_p(p, base, spec.rnd_mode) {
789            b'1'
790        } else if base == 2 {
791            e -= 1;
792            b'1'
793        } else {
794            // base 16: form the leading digit from the top 4 bits of the top significand limb
795            let msl = sig.limbs().next_back().unwrap();
796            const RND_BIT: u64 = Limb::WIDTH - 5;
797            let mut digit = u8::exact_from(msl >> const { RND_BIT + 1 });
798            // Round the digit up only if the value actually has bits below the top nibble. MPFR
799            // 4.2.2 omits this exactness check — an upstream bug that rounds exact values away
800            // (e.g. "%.0RUa" of 1.5 gives 0xdp-3 = 1.625 instead of 0xcp-3) and overflows its digit
801            // table when the top digit is 0xf ("%.0RUa" of 15 prints garbage). With the check, an
802            // all-ones nibble with remaining bits always lands in `next_base_power_p` first, so
803            // digit <= 15 here.
804            if (is_like_rnda(spec.rnd_mode, p.is_sign_negative()) && one_digit_is_inexact(sig, 4))
805                || (spec.rnd_mode == Nearest && (msl & const { Limb::ONE << RND_BIT }) != 0)
806            {
807                digit += 1;
808            }
809            debug_assert!(digit <= 15);
810            e -= 4;
811            digit_to_display_byte_lower(digit).unwrap()
812        };
813        (vec![digit_byte], e)
814    };
815    // all digits in upper case for 'A'
816    if uppercase {
817        digits.make_ascii_uppercase();
818    }
819    np.ip = vec![digits[0]];
820
821    if spec.spec == b'b' || spec.prec != 0 {
822        // the sign and the first digit have been skipped
823        let mut frac = &digits[1..];
824        if spec.prec < 0 {
825            frac = strip_trailing_zeros(frac);
826        }
827        let str_len = frac.len();
828        if str_len != 0 {
829            np.fp = frac.to_vec();
830            if spec.prec > 0 && i64::exact_from(str_len) < spec.prec {
831                // Unreachable: with an explicit precision, `mpfr_get_str_wrapper` returns exactly
832                // `spec.prec + 1` digits, so `str_len` (after the leading digit) equals
833                // `spec.prec`. (Unlike the decimal `regular_eg`/`regular_fg`, there is no
834                // `%g`-style path here that hands `regular_ab` a shorter cached string.)
835                fail_on_untested_path("regular_ab, trailing-zero pad");
836                np.fp_trailing_zeros = spec.prec - i64::exact_from(str_len);
837            }
838        }
839    }
840
841    // decimal point
842    if !np.fp.is_empty() || spec.alt {
843        np.point = b'.';
844    }
845
846    np.exp = exponent_part(if uppercase { b'P' } else { b'p' }, exp, 1);
847    Some(())
848}
849
850// Determines the different parts of the string representation of `p` according to `spec`, returning
851// them together with the total number of characters to be written, or `None` on overflow.
852//
853// This is `partition_number` from `vasprintf.c`, MPFR 4.2.2.
854fn partition_number(p: &Float, mut spec: PrintfSpec) -> Option<(NumberParts, i64)> {
855    let mut np = number_parts_init();
856    // left justification means right space padding
857    np.pad_type = if spec.left {
858        PadType::Right
859    } else if spec.pad == b'0' {
860        PadType::LeadingZeros
861    } else {
862        PadType::Left
863    };
864    let uppercase = matches!(spec.spec, b'A' | b'E' | b'F' | b'G');
865    // the sign/space rule is the same for all cases
866    np.sign = if p.is_sign_negative() {
867        b'-'
868    } else if spec.showsign {
869        b'+'
870    } else if spec.space {
871        b' '
872    } else {
873        b'\0'
874    };
875
876    match p {
877        Float(NaN) => {
878            if matches!(np.pad_type, PadType::LeadingZeros) {
879                // don't want "0000nan"; use left-space padding instead
880                np.pad_type = PadType::Left;
881            }
882            np.ip = if uppercase { b"NAN" } else { b"nan" }.to_vec();
883        }
884        Float(Infinity { .. }) => {
885            if matches!(np.pad_type, PadType::LeadingZeros) {
886                np.pad_type = PadType::Left;
887            }
888            np.ip = if uppercase { b"INF" } else { b"inf" }.to_vec();
889        }
890        Float(Zero { .. }) => {
891            // Note: for 'g', zero is displayed 'f'-style with precision spec.prec - 1 and the
892            // trailing zeros removed unless the '#' flag is used.
893            if matches!(spec.spec, b'a' | b'A') {
894                np.prefix = if uppercase { b"0X" } else { b"0x" };
895            }
896            np.ip = vec![b'0'];
897            if spec.prec < 0 {
898                // empty precision field
899                spec.prec = match spec.spec {
900                    // Malachite zeros are precision-less (unlike MPFR, which sizes this from the
901                    // zero's stored precision), so use precision 1.
902                    b'e' | b'E' => i64::exact_from(get_str_ndigits(10, 1)) - 1,
903                    b'f' | b'F' | b'g' | b'G' => DEFAULT_DECIMAL_PREC,
904                    _ => spec.prec,
905                };
906            }
907            if spec.prec > 0 && (!matches!(spec.spec, b'g' | b'G') || spec.alt) {
908                np.point = b'.';
909                np.fp_trailing_zeros = if matches!(spec.spec, b'g' | b'G') {
910                    spec.prec - 1
911                } else {
912                    spec.prec
913                };
914                debug_assert!(np.fp_trailing_zeros >= 0);
915            } else if spec.alt {
916                np.point = b'.';
917            }
918            // exponent part
919            match spec.spec {
920                b'e' | b'E' => np.exp = if uppercase { b"E+00" } else { b"e+00" }.to_vec(),
921                b'a' | b'A' | b'b' => np.exp = if uppercase { b"P+0" } else { b"p+0" }.to_vec(),
922                _ => {}
923            }
924        }
925        // pure FP (regular number)
926        Float(Finite {
927            exponent,
928            precision,
929            ..
930        }) => match spec.spec {
931            b'a' | b'A' | b'b' => regular_ab(&mut np, p, &spec)?,
932            b'f' | b'F' => {
933                if spec.prec < 0 {
934                    spec.prec = DEFAULT_DECIMAL_PREC;
935                }
936                regular_fg(&mut np, p, &spec, None, true)?;
937            }
938            b'e' | b'E' => regular_eg(&mut np, p, &spec, None, true)?,
939            _ => {
940                // %g case, using the C99 rules: with T the threshold below and X the exponent that
941                // would be displayed with style 'e' and precision T - 1, if T > X >= -4 the
942                // conversion is style 'f'/'F' with precision T - (X + 1), otherwise style 'e'/'E'
943                // with precision T - 1.
944                let threshold = match spec.prec {
945                    i64::MIN..0 => DEFAULT_DECIMAL_PREC,
946                    0 => 1,
947                    _ => spec.prec,
948                };
949                debug_assert!(threshold >= 1);
950                // Try a smaller threshold for get_str: |p| < 2^EXP(p), so the integer part takes at
951                // most ceil(EXP(p) * log10(2)) digits, and with k = PREC(p) - EXP(p), the
952                // fractional part in base 10 has at most k digits (if k > 0).
953                let exp_p = i64::from(*exponent);
954                let k = i64::exact_from(*precision) - exp_p;
955                let mut e = if exp_p <= 0 {
956                    k
957                } else {
958                    (exp_p + 2) / 3 + if k <= 0 { 0 } else { k }
959                };
960                debug_assert!(e >= 1);
961                if e > threshold {
962                    e = threshold;
963                }
964                // error if e does not fit in a usize (for get_str)
965                let Ok(e) = usize::try_from(e) else {
966                    fail_on_untested_path("partition_number, %g e overflows usize");
967                    return None;
968                };
969                // We need the full significand, so call get_str directly (not the wrapper).
970                let (str, dec_exp, _) = get_str(p, 10, e, spec.rnd_mode).unwrap();
971                let dec_info = DecimalInfo { exp: dec_exp, str };
972                // get_str's significand is in [0.1, 1); we want it in [1, 10).
973                let x = dec_info.exp - 1;
974                if threshold > x && x >= -4 {
975                    // x may be as low as -4, so the subtraction can overflow for a threshold within
976                    // 3 of i64::MAX; fail like the other size overflows.
977                    spec.prec = threshold.checked_sub(x)?.checked_sub(1)?;
978                    regular_fg(&mut np, p, &spec, Some(&dec_info), spec.alt)?;
979                } else {
980                    spec.prec = threshold - 1;
981                    regular_eg(&mut np, p, &spec, Some(&dec_info), spec.alt)?;
982                }
983            }
984        },
985    }
986
987    // Compute the number of characters to be written, checking against i64::MAX (MPFR_INTMAX_MAX)
988    // via a wider accumulator.
989    let mut total: i128 = i128::from(np.sign != b'\0');
990    total += np.prefix.len() as i128;
991    total += np.ip.len() as i128;
992    total += i128::from(np.ip_trailing_digits);
993    debug_assert!(np.ip.len() as i128 + i128::from(np.ip_trailing_digits) >= 1);
994    if np.thousands_sep != b'\0' {
995        total += (np.ip.len() as i128 + i128::from(np.ip_trailing_digits) - 1) / 3;
996    }
997    if np.point != b'\0' {
998        total += 1;
999    }
1000    total += i128::from(np.fp_leading_zeros);
1001    total += np.fp.len() as i128;
1002    total += i128::from(np.fp_trailing_zeros);
1003    total += np.exp.len() as i128;
1004
1005    if i128::from(spec.width) > total {
1006        // pad with spaces or zeros depending on np.pad_type
1007        np.pad_size = spec.width - i64::exact_from(total);
1008        total = i128::from(spec.width);
1009    }
1010    if total > i128::from(i64::MAX) {
1011        fail_on_untested_path("partition_number, total width overflows i64");
1012        return None;
1013    }
1014    Some((np, i64::exact_from(total)))
1015}
1016
1017// Prints `p` into `buf` according to `spec`, appending to any existing contents. Returns `None` on
1018// a size overflow.
1019//
1020// This is `sprnt_fp` from `vasprintf.c`, MPFR 4.2.2.
1021fn sprnt_fp(buf: &mut Vec<u8>, p: &Float, spec: &PrintfSpec) -> Option<()> {
1022    let (np, length) = partition_number(p, *spec)?;
1023    // MPFR sizes its buffer from the total length up front; reserve to match.
1024    buf.reserve(usize::try_from(length).unwrap_or(0));
1025    // right justification padding with left spaces
1026    if matches!(np.pad_type, PadType::Left) {
1027        buffer_pad(buf, b' ', np.pad_size);
1028    }
1029    // sign character (may be '-', '+', ' ', or '\0')
1030    if np.sign != b'\0' {
1031        buf.push(np.sign);
1032    }
1033    // prefix part
1034    buf.extend_from_slice(np.prefix);
1035    // right justification padding with leading zeros
1036    if matches!(np.pad_type, PadType::LeadingZeros) {
1037        buffer_pad(buf, b'0', np.pad_size);
1038    }
1039    // integral part (never empty)
1040    if np.thousands_sep != b'\0' {
1041        buffer_sandwich(
1042            buf,
1043            &np.ip,
1044            usize::exact_from(np.ip_trailing_digits),
1045            np.thousands_sep,
1046        );
1047    } else {
1048        buf.extend_from_slice(&np.ip);
1049        // possible trailing zero in the integral part
1050        debug_assert!(np.ip_trailing_digits <= 1);
1051        if np.ip_trailing_digits != 0 {
1052            buf.push(b'0');
1053        }
1054    }
1055    // decimal point
1056    if np.point != b'\0' {
1057        buf.push(np.point);
1058    }
1059    // leading zeros in the fractional part
1060    buffer_pad(buf, b'0', np.fp_leading_zeros);
1061    // significant digits in the fractional part
1062    buf.extend_from_slice(&np.fp);
1063    // trailing zeros in the fractional part
1064    buffer_pad(buf, b'0', np.fp_trailing_zeros);
1065    // exponent part
1066    buf.extend_from_slice(&np.exp);
1067    // left justification padding with right spaces
1068    if matches!(np.pad_type, PadType::Right) {
1069        buffer_pad(buf, b' ', np.pad_size);
1070    }
1071    Some(())
1072}
1073
1074// Builds a [`PrintfSpec`] for a single `%R<conv>` conversion with the given precision (negative
1075// means unset), field width, and rounding mode. The flag fields start out cleared, so callers (e.g.
1076// a future `Display` wiring, which needs `alt`/`showsign`/`left`/`pad`) may set them on the result
1077// before calling [`format_float`].
1078pub_const_crate_test! {float_conversion_spec(
1079    conv: u8,
1080    prec: i64,
1081    width: i64,
1082    rm: RoundingMode,
1083) -> PrintfSpec {
1084    let mut spec = specinfo_init();
1085    spec.spec = conv;
1086    spec.prec = prec;
1087    spec.width = width;
1088    spec.rnd_mode = rm;
1089    spec
1090}}
1091
1092// Formats the [`Float`] `p` for a single `%R<conv>` conversion described by `spec`, returning the
1093// formatted string, or `None` on an internal size overflow (where MPFR returns -1). This is the
1094// core of the `%R` path; [`format`] is the multi-conversion format-string frontend on top of it.
1095pub_crate_test! {format_float(p: &Float, spec: &PrintfSpec) -> Option<String> {
1096    let mut buf = Vec::new();
1097    sprnt_fp(&mut buf, p, spec)?;
1098    Some(String::from_utf8(buf).unwrap())
1099}}
1100
1101// An argument supplied to [`format`]. The `%R<conv>` conversions consume a `Float`; the `*`
1102// width/precision fields and the `%d`/`%i` conversions consume an `Int`; `%s` consumes a `Str`.
1103// This replaces the C `va_list`. The enum is `pub` under `test_build` so the tests can drive
1104// [`format`] directly.
1105pub_crate_test_enum! {
1106PrintfArg<'a> {
1107    Float(&'a Float),
1108    // `format` consumes `Int` and `Str` arguments, but until the multi-conversion frontend becomes
1109    // public, only the tests construct them (`format_float_str` supplies a single `Float`), so in a
1110    // normal build they are never-constructed variants.
1111    #[allow(dead_code)]
1112    Int(i64),
1113    #[allow(dead_code)]
1114    Str(&'a str),
1115}}
1116
1117// Whether `c` is one of the floating-point conversion specifiers.
1118const fn is_float_conversion(c: u8) -> bool {
1119    matches!(
1120        c,
1121        b'a' | b'A' | b'b' | b'e' | b'E' | b'f' | b'F' | b'g' | b'G'
1122    )
1123}
1124
1125// Reads a decimal integer or a `*` field from the front of `fmt`, returning the value and the
1126// unconsumed tail. A `*` consumes the next `Int` argument. The value is `None` if a literal
1127// overflows an `i64` (the C `READ_INT` macro sets an overflow flag that makes `mpfr_vasnprintf_aux`
1128// fail with EOVERFLOW; here the caller bails out likewise).
1129fn read_int<'a>(
1130    mut fmt: &'a [u8],
1131    args: &mut core::slice::Iter<PrintfArg>,
1132) -> (Option<i64>, &'a [u8]) {
1133    if let Some((&b'*', tail)) = fmt.split_first() {
1134        let n = match args.next() {
1135            Some(PrintfArg::Int(n)) => *n,
1136            _ => 0,
1137        };
1138        (Some(n), tail)
1139    } else {
1140        let mut n: Option<i64> = Some(0);
1141        while let Some((&d, tail)) = fmt.split_first()
1142            && d.is_ascii_digit()
1143        {
1144            n = n
1145                .and_then(|n| n.checked_mul(10))
1146                .and_then(|n| n.checked_add(i64::from(d - b'0')));
1147            fmt = tail;
1148        }
1149        (n, fmt)
1150    }
1151}
1152
1153// Applies the field width and flags of `spec` to the already-rendered `body` (with `sign` prepended
1154// if present), padding with spaces or leading zeros. `zero_ok` is false for conversions where the
1155// `0` flag is ignored (e.g. `%s`).
1156fn pad_to_width(
1157    out: &mut Vec<u8>,
1158    sign: Option<u8>,
1159    body: &[u8],
1160    spec: &PrintfSpec,
1161    zero_ok: bool,
1162) {
1163    let core_len = body.len() + usize::from(sign.is_some());
1164    let width = usize::try_from(spec.width).unwrap_or(0);
1165    let pad = width.saturating_sub(core_len);
1166    if spec.left {
1167        if let Some(s) = sign {
1168            out.push(s);
1169        }
1170        out.extend_from_slice(body);
1171        out.resize(out.len() + pad, b' ');
1172    } else if zero_ok && spec.pad == b'0' && spec.prec < 0 {
1173        if let Some(s) = sign {
1174            out.push(s);
1175        }
1176        out.resize(out.len() + pad, b'0');
1177        out.extend_from_slice(body);
1178    } else {
1179        out.resize(out.len() + pad, b' ');
1180        if let Some(s) = sign {
1181            out.push(s);
1182        }
1183        out.extend_from_slice(body);
1184    }
1185}
1186
1187// Formats a signed integer for the `%d`/`%i` conversions, honoring the precision (minimum digits),
1188// field width, and the `-`/`0`/`+`/space/`'` flags.
1189fn format_int(n: i64, spec: &PrintfSpec) -> Vec<u8> {
1190    let neg = n < 0;
1191    let mag = n.unsigned_abs();
1192    let mut digits = format!("{mag}").into_bytes();
1193    if spec.prec >= 0 {
1194        if spec.prec == 0 && mag == 0 {
1195            // precision 0 with value 0 produces no digits
1196            digits.clear();
1197        } else if let Ok(want) = usize::try_from(spec.prec)
1198            && digits.len() < want
1199        {
1200            let mut d = vec![b'0'; want - digits.len()];
1201            d.extend_from_slice(&digits);
1202            digits = d;
1203        }
1204    }
1205    if spec.group && digits.len() > 3 {
1206        // The `'` flag; a comma, like the float path (and like it, the padding zeros from the `0`
1207        // flag are not grouped).
1208        let len = digits.len();
1209        let mut grouped = Vec::with_capacity(len + (len - 1) / 3);
1210        // the number of digits in the leftmost block
1211        let r = (len - 1) % 3 + 1;
1212        grouped.extend_from_slice(&digits[..r]);
1213        for chunk in digits[r..].chunks(3) {
1214            grouped.push(b',');
1215            grouped.extend_from_slice(chunk);
1216        }
1217        digits = grouped;
1218    }
1219    let sign = if neg {
1220        Some(b'-')
1221    } else if spec.showsign {
1222        Some(b'+')
1223    } else if spec.space {
1224        Some(b' ')
1225    } else {
1226        None
1227    };
1228    let mut out = Vec::new();
1229    pad_to_width(&mut out, sign, &digits, spec, true);
1230    out
1231}
1232
1233// Formats a string for the `%s` conversion, honoring the precision (the maximum length in bytes,
1234// rounded down to a character boundary so that the output remains valid UTF-8) and field width.
1235fn format_str(s: &str, spec: &PrintfSpec) -> Vec<u8> {
1236    let s = if spec.prec >= 0 {
1237        let mut n = usize::try_from(spec.prec)
1238            .unwrap_or(usize::MAX)
1239            .min(s.len());
1240        while !s.is_char_boundary(n) {
1241            n -= 1;
1242        }
1243        &s[..n]
1244    } else {
1245        s
1246    };
1247    let mut out = Vec::new();
1248    pad_to_width(&mut out, None, s.as_bytes(), spec, false);
1249    out
1250}
1251
1252// Interprets an MPFR-style format string, consuming `args` from left to right, and returns the
1253// result, or `None` on failure (where MPFR's `mpfr_vasnprintf_aux` returns -1): a width or
1254// precision literal overflowing an `i64`, an internal size overflow, a missing or wrongly-typed
1255// argument, or a conversion that is valid in MPFR but has no counterpart in the `PrintfArg` model.
1256// Supports the `%R<conv>` float conversions (all flags, field width, precision, rounding, and bases
1257// 2/10/16), plus `%d`/`%i`, `%s`, `%%`, and `*` width/precision. Invalid conversion specifications
1258// are dropped (matching MPFR's "the behavior is undefined" choice of not emitting them). The `%n`
1259// conversion is intentionally unsupported.
1260//
1261// This is the `%R` path of `mpfr_vasnprintf_aux`'s main loop from `vasprintf.c`, MPFR 4.2.2, recast
1262// onto a Rust argument slice instead of a `va_list` (and without the `gmp_vsnprintf` delegation,
1263// which has no Malachite analog).
1264pub_crate_test! {format(fmt: &[u8], args: &[PrintfArg]) -> Option<Vec<u8>> {
1265    let mut out = Vec::new();
1266    let mut fmt = fmt;
1267    let mut args = args.iter();
1268    while let Some(&c) = fmt.first() {
1269        if c != b'%' {
1270            out.push(c);
1271            fmt = &fmt[1..];
1272            continue;
1273        }
1274        // c == '%'
1275        fmt = &fmt[1..];
1276        if fmt.first() == Some(&b'%') {
1277            out.push(b'%');
1278            fmt = &fmt[1..];
1279            continue;
1280        }
1281
1282        let mut spec = specinfo_init();
1283        fmt = parse_flags(fmt, &mut spec);
1284
1285        // field width
1286        let (w, rest) = read_int(fmt, &mut args);
1287        fmt = rest;
1288        spec.width = w?;
1289        if spec.width < 0 {
1290            // a negative width (from `*`) means left justification
1291            spec.left = true;
1292            spec.width = spec.width.saturating_neg();
1293        }
1294
1295        // precision
1296        spec.prec = if fmt.first() == Some(&b'.') {
1297            fmt = &fmt[1..];
1298            let (pr, rest) = read_int(fmt, &mut args);
1299            fmt = rest;
1300            let pr = pr?;
1301            if pr < 0 { -1 } else { pr }
1302        } else {
1303            -1
1304        };
1305
1306        fmt = parse_arg_type(fmt, &mut spec);
1307
1308        // rounding mode (an optional character, only for the mpfr argument type)
1309        if spec.arg_type == ArgType::Mpfr
1310            && let Some((&c, tail)) = fmt.split_first()
1311        {
1312            let rm = match c {
1313                b'D' => Some(Floor),
1314                b'U' => Some(Ceiling),
1315                b'Y' => Some(Up),
1316                b'Z' => Some(Down),
1317                b'N' => Some(Nearest),
1318                // MPFR's rounding-mode enum: 0 = RNDN, 1 = RNDZ, 2 = RNDU, 3 = RNDD, 4 = RNDA
1319                b'*' => Some(match args.next() {
1320                    Some(PrintfArg::Int(1)) => Down,
1321                    Some(PrintfArg::Int(2)) => Ceiling,
1322                    Some(PrintfArg::Int(3)) => Floor,
1323                    Some(PrintfArg::Int(4)) => Up,
1324                    _ => Nearest,
1325                }),
1326                _ => None,
1327            };
1328            if let Some(rm) = rm {
1329                spec.rnd_mode = rm;
1330                fmt = tail;
1331            }
1332        }
1333
1334        let Some((&conversion, tail)) = fmt.split_first() else {
1335            break;
1336        };
1337        spec.spec = conversion;
1338        fmt = tail;
1339        if !specinfo_is_valid(spec) {
1340            // invalid conversion specifier: drop it
1341            continue;
1342        }
1343
1344        // Every conversion must consume exactly one argument (or fail): a valid conversion that
1345        // silently consumed nothing would desynchronize the argument stream for every later
1346        // conversion.
1347        match (spec.spec, spec.arg_type) {
1348            (c, ArgType::Mpfr) if is_float_conversion(c) => {
1349                let PrintfArg::Float(p) = args.next()? else {
1350                    return None;
1351                };
1352                sprnt_fp(&mut out, p, &spec)?;
1353            }
1354            (
1355                b'd' | b'i',
1356                ArgType::None
1357                | ArgType::Char
1358                | ArgType::Short
1359                | ArgType::Long
1360                | ArgType::LongLong
1361                | ArgType::IntMax
1362                | ArgType::Size
1363                | ArgType::PtrDiff,
1364            ) => {
1365                let PrintfArg::Int(n) = args.next()? else {
1366                    return None;
1367                };
1368                out.extend_from_slice(&format_int(*n, &spec));
1369            }
1370            (b's', ArgType::None) => {
1371                let PrintfArg::Str(s) = args.next()? else {
1372                    return None;
1373                };
1374                out.extend_from_slice(&format_str(s, &spec));
1375            }
1376            // A conversion that is valid in MPFR but has no counterpart in the `PrintfArg` model
1377            // (e.g. `%Zd`, `%u`, `%x`, `%c`, `%p`, `%ls`, or a float conversion without the `R`
1378            // prefix): fail rather than desynchronize the argument stream.
1379            _ => return None,
1380        }
1381    }
1382    Some(out)
1383}}
1384
1385/// Formats a [`Float`] according to an MPFR-style `printf` format string, for strict compatibility
1386/// with MPFR's `mpfr_printf` family.
1387///
1388/// The format string should contain a single conversion consuming the [`Float`], written
1389/// `%[flags][width][.precision]R[rounding]conv`, with any surrounding literal text (a literal `%`
1390/// is written `%%`). The pieces are:
1391/// - **flags**: any of `-` (left-justify within the field), `+` (always show a sign), space (show a
1392///   space before a nonnegative value), `#` (alternate form: always print a radix point, and keep
1393///   trailing zeros for `g`/`G`), `0` (pad the field with leading zeros), and `'` (group the
1394///   integer part into thousands separated by `,`).
1395/// - **width**: the minimum field width, as a decimal integer.
1396/// - **precision**: following a `.`, the number of digits after the radix point (for `e`/`f` and
1397///   their hexadecimal/binary analogues) or the number of significant digits (for `g`); it defaults
1398///   to 6.
1399/// - **`R`**: marks the argument as a [`Float`] (MPFR's length modifier).
1400/// - **rounding**: an optional MPFR rounding character — `N` (to nearest, the default), `D`
1401///   (toward $-\infty$), `U` (toward $+\infty$), `Y` (away from zero), or `Z` (toward zero).
1402/// - **conv**: the conversion — `e`/`E` (scientific), `f`/`F` (fixed-point), `g`/`G` (general),
1403///   `a`/`A` (hexadecimal significand with a binary exponent), or `b` (binary significand with a
1404///   binary exponent).
1405///
1406/// Returns `None` when the format string is not a single well-formed [`Float`] conversion: for
1407/// instance if it uses `*` for the width or precision (which would need an integer argument that
1408/// this single-value entry point does not supply), contains no `%R` conversion or more than one,
1409/// requests a width or precision that overflows, or would produce an over-long result.
1410///
1411/// # Worst-case complexity
1412/// $T(n) = O(n (\log n)^2 \log\log n)$
1413///
1414/// $M(n) = O(n \log n)$
1415///
1416/// where $T$ is time, $M$ is additional memory, and $n$ is `max(x.complexity(), p, w)`, with `p`
1417/// and `w` the precision and field width requested by the format string.
1418///
1419/// # Examples
1420/// ```
1421/// use malachite_float::float::conversion::string::format_float::format_float_str;
1422/// use malachite_float::Float;
1423///
1424/// // fixed-point, scientific, and hexadecimal conversions
1425/// assert_eq!(
1426///     format_float_str(&Float::from(1.5), "%.3Rf"),
1427///     Some("1.500".to_string())
1428/// );
1429/// assert_eq!(
1430///     format_float_str(&Float::from(1.5), "%.5Re"),
1431///     Some("1.50000e+00".to_string())
1432/// );
1433/// assert_eq!(
1434///     format_float_str(&Float::from(255.0), "%Ra"),
1435///     Some("0xf.fp+4".to_string())
1436/// );
1437///
1438/// // surrounding literal text is copied; a rounding character overrides the default of nearest
1439/// assert_eq!(
1440///     format_float_str(&Float::from(1.5), "x = %Rg!"),
1441///     Some("x = 1.5!".to_string())
1442/// );
1443/// assert_eq!(
1444///     format_float_str(&Float::from(1.5), "%.0RUf"),
1445///     Some("2".to_string())
1446/// );
1447///
1448/// // `*` needs an integer argument that this single-value entry point does not provide
1449/// assert_eq!(format_float_str(&Float::from(1.5), "%*Rf"), None);
1450/// ```
1451///
1452/// A single-value entry point over the port of the `%R` path of `mpfr_vasnprintf_aux` (vasprintf.c,
1453/// MPFR 4.2.2): `format_float_str(x, fmt)` is `format(fmt, &[PrintfArg::Float(x)])`. The output is
1454/// valid UTF-8 because every literal run of `fmt` (`%` is ASCII, so it never splits a multi-byte
1455/// character) and every conversion's output is.
1456#[inline]
1457pub fn format_float_str(x: &Float, fmt: &str) -> Option<String> {
1458    format(fmt.as_bytes(), &[PrintfArg::Float(x)]).map(|v| String::from_utf8(v).unwrap())
1459}