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