malachite_base/strings/gmp_format.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MP Library.
4//
5// Copyright © 1993-2019 Free Software Foundation, Inc.
6//
7// This file is part of Malachite.
8//
9// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
10// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
11// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
12
13use crate::num::conversion::traits::{ToStringBase, WrappingFrom};
14use alloc::string::String;
15use alloc::vec::Vec;
16use core::cmp::max;
17
18/// A single parsed `printf`-style conversion specification, as GMP's and MPFR's formatted-output
19/// functions understand them: `% [flags] [width] [.precision] [type] [rounding] conv`.
20///
21/// The struct is purely syntactic: it records what was written, and each [`GmpFormatArg`]
22/// implementation applies its own library's interpretation. In particular:
23/// - `sign` is the *last* `+` or space flag written (or 0 for neither), which is what GMP's own
24/// types use, while `plus` and `space` record whether each flag appeared at all, which is what
25/// the C conversions need (`+` overrides space in C, regardless of order).
26/// - `prec` is [`None`] when no precision was written, and `Some(-1)` for a `.` with no digits,
27/// which GMP reads as "all necessary digits" and C and MPFR read as 0.
28/// - `type_chr` is the type or length-modifier character (`Z`, `Q`, `R`, `l`, `h`, and so on, or 0
29/// for none), with `type_doubled` distinguishing `hh` and `ll`. As in GMP's parser, a later type
30/// character overwrites an earlier one.
31/// - `rnd_chr` is MPFR's rounding character, only ever set directly after an `R`.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33pub struct GmpConversionSpec {
34 pub sign: u8,
35 pub plus: bool,
36 pub space: bool,
37 pub alt: bool,
38 pub left: bool,
39 pub group: bool,
40 pub fill: u8,
41 pub width: i64,
42 pub prec: Option<i64>,
43 pub type_chr: u8,
44 pub type_doubled: bool,
45 pub rnd_chr: u8,
46 pub conv: u8,
47}
48
49// Reads a run of decimal digits (the first of which is `c`) from the front of `fmt`, returning the
50// value and the unconsumed tail. GMP stores widths and precisions in a C `int`, so values beyond
51// `i32::MAX` cannot be expressed in a format string and are rejected.
52fn read_digits(c: u8, mut fmt: &[u8]) -> Option<(i64, &[u8])> {
53 let mut n = i64::from(c - b'0');
54 while let Some((&d, tail)) = fmt.split_first()
55 && d.is_ascii_digit()
56 {
57 n = n.checked_mul(10)?.checked_add(i64::from(d - b'0'))?;
58 fmt = tail;
59 }
60 if n > const { i32::MAX as i64 } {
61 return None;
62 }
63 Some((n, fmt))
64}
65
66/// Parses a single conversion specification, starting just after the `%`, and returns it along with
67/// the unconsumed tail. `star` supplies the value of a `*` field width or precision, from the
68/// argument list; it may fail, and a caller with no argument list simply passes `&mut || None`.
69///
70/// The parser mirrors GMP's single-pass structure, including its quirks: flags may appear after the
71/// width, a later `+` or space flag overwrites the `sign` of an earlier one, a later type character
72/// overwrites an earlier one, and a `-` flag does not reset the `0` flag's fill character. A
73/// negative `*` width means left justification, and a negative `*` precision is treated as 0.
74/// Returns [`None`] if a width or precision overflows the range of a C `int` (beyond which GMP
75/// cannot express them), if `star` fails, or on a character with no role in a conversion
76/// specification.
77///
78/// # Worst-case complexity
79/// $T(n) = O(n)$
80///
81/// $M(n) = O(1)$
82///
83/// where $T$ is time, $M$ is additional memory, and $n$ is `fmt.len()`.
84///
85/// # Examples
86/// ```
87/// use malachite_base::strings::gmp_format::parse_gmp_conversion_spec;
88///
89/// let (spec, rest) = parse_gmp_conversion_spec(b"+8.3Zd tail", &mut || None).unwrap();
90/// assert_eq!(spec.sign, b'+');
91/// assert_eq!(spec.width, 8);
92/// assert_eq!(spec.prec, Some(3));
93/// assert_eq!(spec.type_chr, b'Z');
94/// assert_eq!(spec.conv, b'd');
95/// assert_eq!(rest, b" tail");
96/// ```
97///
98/// This is the format-parsing loop of `__gmp_doprnt` from `printf/doprnt.c`, GMP 6.3.0, with MPFR's
99/// `R` type and rounding characters from `vasprintf.c`, MPFR 4.2.2.
100pub fn parse_gmp_conversion_spec<'a>(
101 mut fmt: &'a [u8],
102 star: &mut dyn FnMut() -> Option<i64>,
103) -> Option<(GmpConversionSpec, &'a [u8])> {
104 let mut spec = GmpConversionSpec {
105 sign: 0,
106 plus: false,
107 space: false,
108 alt: false,
109 left: false,
110 group: false,
111 fill: b' ',
112 width: 0,
113 prec: None,
114 type_chr: 0,
115 type_doubled: false,
116 rnd_chr: 0,
117 conv: 0,
118 };
119 let mut in_width = true;
120 loop {
121 let (&c, tail) = fmt.split_first()?;
122 fmt = tail;
123 match c {
124 b'#' => spec.alt = true,
125 b'\'' => spec.group = true,
126 b'+' => {
127 spec.plus = true;
128 spec.sign = c;
129 }
130 b' ' => {
131 spec.space = true;
132 spec.sign = c;
133 }
134 b'-' => spec.left = true,
135 b'0' => {
136 if in_width {
137 // in the width field, `0` is a flag setting the fill
138 spec.fill = b'0';
139 } else {
140 spec.prec = Some(0);
141 }
142 }
143 b'1'..=b'9' => {
144 let (n, tail) = read_digits(c, fmt)?;
145 fmt = tail;
146 if in_width {
147 spec.width = n;
148 } else {
149 spec.prec = Some(n);
150 }
151 }
152 b'.' => {
153 // `.` alone is `Some(-1)`; any following digits overwrite it
154 spec.prec = Some(-1);
155 in_width = false;
156 }
157 b'*' => {
158 let n = star()?;
159 if n.unsigned_abs() > const { i32::MAX as u64 } {
160 return None;
161 }
162 if in_width {
163 spec.width = if n < 0 {
164 // a negative width means left justification
165 spec.left = true;
166 -n
167 } else {
168 n
169 };
170 } else {
171 // a negative precision is not allowed
172 spec.prec = Some(max(0, n));
173 }
174 }
175 b'h' | b'l' => {
176 spec.type_chr = c;
177 spec.type_doubled = false;
178 if let Some((&d, tail)) = fmt.split_first()
179 && d == c
180 {
181 spec.type_doubled = true;
182 fmt = tail;
183 }
184 }
185 b'j' | b'q' | b't' | b'z' | b'L' | b'Q' | b'M' | b'N' | b'Z' | b'P' => {
186 spec.type_chr = c;
187 spec.type_doubled = false;
188 }
189 b'R' => {
190 spec.type_chr = c;
191 spec.type_doubled = false;
192 // MPFR's rounding character comes directly after the `R`; `*`, which in C fetches
193 // the mode from the argument list, is not supported
194 if let Some((&d, tail)) = fmt.split_first() {
195 match d {
196 b'N' | b'D' | b'U' | b'Y' | b'Z' => {
197 spec.rnd_chr = d;
198 fmt = tail;
199 }
200 b'*' => return None,
201 _ => {}
202 }
203 }
204 }
205 b'F' => {
206 if spec.type_chr == b'R' {
207 // after an `R`, `F` is MPFR's uppercase fixed-point conversion
208 spec.conv = c;
209 return Some((spec, fmt));
210 }
211 // elsewhere it is GMP's `mpf_t` type character
212 spec.type_chr = c;
213 spec.type_doubled = false;
214 }
215 b'd' | b'i' | b'u' | b'o' | b'x' | b'X' | b'e' | b'E' | b'f' | b'g' | b'G' | b'a'
216 | b'A' | b'b' | b'c' | b's' | b'p' | b'n' | b'm' => {
217 spec.conv = c;
218 return Some((spec, fmt));
219 }
220 _ => return None,
221 }
222 }
223}
224
225/// A value that can be consumed by a conversion of a GMP-style format string; see [`gmp_format`].
226///
227/// Each implementation accepts the conversions its library counterpart would: `Natural` and
228/// `Integer` take `%Z` integer conversions, `Rational` takes `%Q`, `Float` takes `%R`, primitive
229/// integers take the plain C integer conversions (and `%c`), [`char`] takes `%c`, and strings take
230/// `%s`. [`gmp_format`](GmpFormatArg::gmp_format) returns the formatted piece, or [`None`] when the
231/// specification does not apply to the value's type.
232pub trait GmpFormatArg {
233 /// Formats this value according to a single parsed conversion specification, or returns
234 /// [`None`] when the specification does not apply to this type.
235 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String>;
236
237 /// The integer consumed by a `*` field width or precision, when this value is a primitive
238 /// integer that fits in an `i64`.
239 fn printf_int(&self) -> Option<i64> {
240 None
241 }
242}
243
244// Appends `n` copies of `fill` to `out`.
245fn pad(out: &mut Vec<u8>, fill: u8, n: usize) {
246 out.resize(out.len() + n, fill);
247}
248
249// Applies the field width of `spec` to the already-rendered `body`, left- or right-justifying it.
250// Used by the conversions whose zero-fill handling is trivial (`%c`, `%s`): the fill is always a
251// space, as in C.
252fn justify(body: &[u8], spec: &GmpConversionSpec) -> Option<String> {
253 let width = usize::try_from(spec.width).unwrap_or(0);
254 let padding = width.saturating_sub(body.len());
255 let mut out = Vec::with_capacity(body.len() + padding);
256 if !spec.left {
257 pad(&mut out, b' ', padding);
258 }
259 out.extend_from_slice(body);
260 if spec.left {
261 pad(&mut out, b' ', padding);
262 }
263 // `body` comes from a `str` or is ASCII
264 String::from_utf8(out).ok()
265}
266
267// Whether `spec` is an integer conversion with no type character or with a C length modifier, which
268// is accepted but has no effect: the value is formatted as passed, and is never truncated the way
269// C's `%hd` would truncate an `int` argument.
270const fn is_c_integer_spec(spec: &GmpConversionSpec) -> bool {
271 matches!(spec.conv, b'd' | b'i' | b'u' | b'o' | b'x' | b'X')
272 && matches!(
273 spec.type_chr,
274 0 | b'h' | b'l' | b'j' | b'q' | b't' | b'z' | b'L'
275 )
276}
277
278// Formats a primitive integer with sign `neg` and absolute-value digits produced by `to_base`,
279// following the C `printf` rules: `+` overrides the space flag, the `0` flag is ignored with left
280// justification or an explicit precision, a `#` prefix is applied only when the digits do not
281// already begin with a zero, and a zero value with a precision of 0 produces no digits. In the C
282// locale the `'` flag groups nothing, so it is accepted and ignored.
283//
284// This is the behavior `gmp_printf` gets by handing the standard conversions to the C library.
285fn format_c_integer(
286 neg: bool,
287 to_base: &dyn Fn(u8, bool) -> String,
288 spec: &GmpConversionSpec,
289) -> Option<String> {
290 if !is_c_integer_spec(spec) {
291 return None;
292 }
293 let digits = match spec.conv {
294 b'o' => to_base(8, false),
295 b'x' => to_base(16, false),
296 b'X' => to_base(16, true),
297 _ => to_base(10, false),
298 };
299 let mut s = digits.as_bytes();
300 let sign = if neg {
301 b'-'
302 } else if spec.plus {
303 b'+'
304 } else if spec.space {
305 b' '
306 } else {
307 0
308 };
309 let sign_len = usize::from(sign != 0);
310 // C reads a `.` with no digits (`Some(-1)`) as a precision of 0
311 let prec = spec.prec.map_or(-1, |p| max(0, p));
312 if prec == 0 && s == b"0" {
313 s = b"";
314 }
315 let mut showbase: &[u8] = if spec.alt {
316 match spec.conv {
317 b'x' => b"0x",
318 b'X' => b"0X",
319 b'o' => b"0",
320 _ => b"",
321 }
322 } else {
323 b""
324 };
325 if s.first() == Some(&b'0') {
326 showbase = b"";
327 }
328 let zeros = usize::try_from(max(0, prec - i64::try_from(s.len()).ok()?)).ok()?;
329 let core = sign_len + showbase.len() + zeros + s.len();
330 let width = usize::try_from(spec.width).unwrap_or(0);
331 let padding = width.saturating_sub(core);
332 // the 0 flag is ignored with left justification or an explicit precision
333 let zero_fill = spec.fill == b'0' && !spec.left && spec.prec.is_none();
334 let mut out = Vec::with_capacity(core + padding);
335 if !spec.left && !zero_fill {
336 pad(&mut out, b' ', padding);
337 }
338 if sign != 0 {
339 out.push(sign);
340 }
341 out.extend_from_slice(showbase);
342 if zero_fill {
343 pad(&mut out, b'0', padding);
344 }
345 pad(&mut out, b'0', zeros);
346 out.extend_from_slice(s);
347 if spec.left {
348 pad(&mut out, b' ', padding);
349 }
350 // ASCII by construction
351 String::from_utf8(out).ok()
352}
353
354// Formats a primitive integer for a `%c` conversion, as C does: the value is converted to an
355// `unsigned char`, keeping its lowest byte.
356fn format_c_char_of_int(value: u64, spec: &GmpConversionSpec) -> Option<String> {
357 if spec.conv != b'c' || !matches!(spec.type_chr, 0 | b'h' | b'l') {
358 return None;
359 }
360 justify(&[u8::wrapping_from(value)], spec)
361}
362
363macro_rules! impl_gmp_format_arg_unsigned {
364 ($t:ident) => {
365 impl GmpFormatArg for $t {
366 /// Formats an unsigned primitive integer according to a single parsed conversion
367 /// specification: a plain C integer conversion (`d`, `i`, `u`, `o`, `x`, or `X`, with
368 /// any C length modifier accepted but not truncating the value), or `c` (keeping the
369 /// value's lowest byte, as C does).
370 ///
371 /// # Worst-case complexity
372 /// $T(n, w, p) = O(n + w + p)$
373 ///
374 /// $M(n, w, p) = O(n + w + p)$
375 ///
376 /// where $T$ is time, $M$ is additional memory, $n$ is `self.significant_bits()`, $w$
377 /// is the field width stored in `spec`, and $p$ is the precision stored in `spec`:
378 /// rendering the digits is linear in the value's bits, and padding to the field width
379 /// or precision is linear in those settings.
380 ///
381 /// # Examples
382 /// See [here](super::gmp_format#gmp_format).
383 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
384 if spec.conv == b'c' {
385 return format_c_char_of_int(u64::wrapping_from(*self), spec);
386 }
387 format_c_integer(
388 false,
389 &|base, upper| {
390 if upper {
391 self.to_string_base_upper(base)
392 } else {
393 self.to_string_base(base)
394 }
395 },
396 spec,
397 )
398 }
399
400 fn printf_int(&self) -> Option<i64> {
401 i64::try_from(*self).ok()
402 }
403 }
404 };
405}
406apply_to_unsigneds!(impl_gmp_format_arg_unsigned);
407
408macro_rules! impl_gmp_format_arg_signed {
409 ($t:ident) => {
410 impl GmpFormatArg for $t {
411 /// Formats a signed primitive integer according to a single parsed conversion
412 /// specification: a plain C integer conversion (`d`, `i`, `u`, `o`, `x`, or `X`, with
413 /// any C length modifier accepted but not truncating the value, and a negative value
414 /// keeping its sign under every conversion), or `c` (keeping the value's lowest byte,
415 /// as C does).
416 ///
417 /// # Worst-case complexity
418 /// $T(n, w, p) = O(n + w + p)$
419 ///
420 /// $M(n, w, p) = O(n + w + p)$
421 ///
422 /// where $T$ is time, $M$ is additional memory, $n$ is `self.significant_bits()`, $w$
423 /// is the field width stored in `spec`, and $p$ is the precision stored in `spec`:
424 /// rendering the digits is linear in the value's bits, and padding to the field width
425 /// or precision is linear in those settings.
426 ///
427 /// # Examples
428 /// See [here](super::gmp_format#gmp_format).
429 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
430 if spec.conv == b'c' {
431 return format_c_char_of_int(u64::wrapping_from(self.unsigned_abs()), spec);
432 }
433 let abs = self.unsigned_abs();
434 format_c_integer(
435 *self < 0,
436 &|base, upper| {
437 if upper {
438 abs.to_string_base_upper(base)
439 } else {
440 abs.to_string_base(base)
441 }
442 },
443 spec,
444 )
445 }
446
447 fn printf_int(&self) -> Option<i64> {
448 i64::try_from(*self).ok()
449 }
450 }
451 };
452}
453apply_to_signeds!(impl_gmp_format_arg_signed);
454
455impl GmpFormatArg for char {
456 /// Formats a [`char`] according to a single parsed conversion specification, which must be a
457 /// `%c` conversion with no type character.
458 ///
459 /// # Worst-case complexity
460 /// $T(w) = O(w)$
461 ///
462 /// $M(w) = O(w)$
463 ///
464 /// where $T$ is time, $M$ is additional memory, and $w$ is the field width stored in `spec`:
465 /// the character itself is constant-size, but the output is padded to the field width.
466 ///
467 /// # Examples
468 /// See [here](super::gmp_format#gmp_format).
469 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
470 if spec.conv != b'c' || spec.type_chr != 0 {
471 return None;
472 }
473 let mut buf = [0; 4];
474 justify(self.encode_utf8(&mut buf).as_bytes(), spec)
475 }
476}
477
478impl GmpFormatArg for &str {
479 /// Formats a string according to a single parsed conversion specification, which must be a `%s`
480 /// conversion with no type character. As in C, the precision is the maximum number of bytes
481 /// written; if that limit would split a multi-byte character, [`None`] is returned, since the
482 /// output could not be a valid string.
483 ///
484 /// # Worst-case complexity
485 /// $T(n) = O(n)$
486 ///
487 /// $M(n) = O(n)$
488 ///
489 /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.len(), w)`, with `w` the
490 /// field width requested by the format string.
491 ///
492 /// # Examples
493 /// See [here](super::gmp_format#gmp_format).
494 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
495 if spec.conv != b's' || spec.type_chr != 0 {
496 return None;
497 }
498 let mut s = *self;
499 if let Some(prec) = spec.prec {
500 let prec = usize::try_from(max(0, prec)).ok()?;
501 if prec < s.len() {
502 if !s.is_char_boundary(prec) {
503 return None;
504 }
505 s = &s[..prec];
506 }
507 }
508 justify(s.as_bytes(), spec)
509 }
510}
511
512impl GmpFormatArg for String {
513 /// Formats a string according to a single parsed conversion specification; see the
514 /// [`&str`](GmpFormatArg#impl-GmpFormatArg-for-%26str) implementation.
515 ///
516 /// # Worst-case complexity
517 /// $T(n) = O(n)$
518 ///
519 /// $M(n) = O(n)$
520 ///
521 /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.len(), w)`, with `w` the
522 /// field width requested by the format string.
523 ///
524 /// # Examples
525 /// See [here](super::gmp_format#gmp_format).
526 #[inline]
527 fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
528 (&**self).gmp_format(spec)
529 }
530}
531
532/// Formats a sequence of values according to a GMP-style `printf` format string, each conversion
533/// consuming the next value, as `gmp_printf` and `mpfr_printf` do.
534///
535/// The format string may contain literal text, `%%` escapes, and any number of conversions, each
536/// written `%[flags][width][.precision][type][rounding]conv`. Which conversions a value accepts is
537/// up to its [`GmpFormatArg`] implementation: `%Z` integer conversions for `Natural` and `Integer`,
538/// `%Q` for `Rational`, `%R` float conversions for `Float`, and the plain C conversions for
539/// primitive integers (`d`, `i`, `u`, `o`, `x`, `X`, `c`), [`char`]s (`c`), and strings (`s`). A
540/// `*` field width or precision consumes the next value, which must be a primitive integer; a
541/// negative `*` width means left justification.
542///
543/// Returns [`None`] when a conversion specification is malformed or requests a width or precision
544/// beyond `i32::MAX` (the range of the C `int` GMP itself stores them in), when a conversion does
545/// not apply to the value it would consume, when there are too few values (extra values are
546/// permitted, as in C), or when the conversion is one this function does not support: `%n`, `%p`,
547/// `%m`, `%F` (GMP's `mpf_t`), `%M` and `%N` (limbs), `%P` (MPFR precisions), and the C float
548/// conversions on primitive floats (use a `Float`).
549///
550/// The [`gmp_format!`](crate::gmp_format) macro wraps this function, building the argument slice.
551///
552/// # Worst-case complexity
553/// $T(n) = O(n (\log n)^2 \log\log n)$
554///
555/// $M(n) = O(n \log n)$
556///
557/// where $T$ is time, $M$ is additional memory, and $n$ is the sum over the conversions of
558/// `max(x.significant_bits(), p, w)`, with `p` and `w` each conversion's precision and field width,
559/// plus `fmt.len()`.
560///
561/// # Examples
562/// ```
563/// use malachite_base::strings::gmp_format::gmp_format;
564///
565/// assert_eq!(
566/// gmp_format("%d + %d = %d", &[&2u32, &2u32, &4u32]),
567/// Some("2 + 2 = 4".to_string())
568/// );
569/// assert_eq!(
570/// gmp_format("%c%s%c", &[&'(', &"hello", &')']),
571/// Some("(hello)".to_string())
572/// );
573/// assert_eq!(
574/// gmp_format("%0*x", &[&8i32, &255u32]),
575/// Some("000000ff".to_string())
576/// );
577/// // 100% literal
578/// assert_eq!(gmp_format("100%%", &[]), Some("100%".to_string()));
579///
580/// // a conversion that does not apply to its value
581/// assert_eq!(gmp_format("%s", &[&5u32]), None);
582/// // too few values
583/// assert_eq!(gmp_format("%d %d", &[&5u32]), None);
584/// ```
585///
586/// This is `gmp_snprintf` from `printf/snprintf.c`, GMP 6.3.0, and `mpfr_snprintf` from
587/// `vasprintf.c`, MPFR 4.2.2, where the buffer is always large enough.
588pub fn gmp_format(fmt: &str, args: &[&dyn GmpFormatArg]) -> Option<String> {
589 let bytes = fmt.as_bytes();
590 let mut out = Vec::new();
591 let mut i = 0;
592 let mut next = 0;
593 while i < bytes.len() {
594 if bytes[i] == b'%' {
595 if bytes.get(i + 1) == Some(&b'%') {
596 out.push(b'%');
597 i += 2;
598 continue;
599 }
600 let (spec, rest) = {
601 let mut star = || {
602 let arg = args.get(next)?;
603 next += 1;
604 arg.printf_int()
605 };
606 parse_gmp_conversion_spec(&bytes[i + 1..], &mut star)?
607 };
608 let arg = args.get(next)?;
609 next += 1;
610 out.extend_from_slice(arg.gmp_format(&spec)?.as_bytes());
611 i = bytes.len() - rest.len();
612 } else {
613 out.push(bytes[i]);
614 i += 1;
615 }
616 }
617 // Literal text is copied byte-for-byte from the input `&str` and every conversion's output is a
618 // `String`, so the output is valid UTF-8.
619 String::from_utf8(out).ok()
620}
621
622/// Formats values according to a GMP-style `printf` format string, as
623/// [`gmp_format`](crate::gmp_format) does, taking the values as ordinary arguments:
624/// `gmp_format!("%Zd of %d", n, k)`.
625///
626/// The result is an `Option<String>`; see [`gmp_format`](crate::gmp_format) for the supported
627/// conversions and failure conditions.
628///
629/// # Examples
630/// ```
631/// use malachite_base::gmp_format;
632///
633/// assert_eq!(
634/// gmp_format!("%d + %d = %d", 2u32, 2u32, 4u32),
635/// Some("2 + 2 = 4".to_string())
636/// );
637/// ```
638#[macro_export]
639macro_rules! gmp_format {
640 ($fmt:expr $(, $args:expr)* $(,)?) => {
641 $crate::strings::gmp_format::gmp_format(
642 $fmt,
643 &[$(&$args as &dyn $crate::strings::gmp_format::GmpFormatArg),*],
644 )
645 };
646}