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