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}