Skip to main content

malachite_nz/integer/conversion/string/
format_integer.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::integer::Integer;
10use crate::natural::conversion::string::format_natural::{format_gmp_integer_spec, format_gmp_str};
11use alloc::string::String;
12use malachite_base::strings::gmp_format::{GmpConversionSpec, GmpFormatArg};
13
14/// Formats an [`Integer`] according to a GMP-style `printf` format string, for strict compatibility
15/// with GMP's `gmp_printf` family.
16///
17/// The format string should contain a single conversion consuming the [`Integer`], written
18/// `%[flags][width][.precision]Z[conv]`, with any surrounding literal text (a literal `%` is
19/// written `%%`). The pieces are:
20/// - **flags**: any of `-` (left-justify within the field), `+` (always show a sign), space (show a
21///   space before a nonnegative value), `#` (alternate form: prefix hexadecimal output with `0x` or
22///   `0X` and octal output with `0`, unless the digits already begin with a zero), and `0` (pad the
23///   field with leading zeros). The `'` flag is accepted but, as in GMP, has no effect on GMP
24///   types.
25/// - **width**: the minimum field width, as a decimal integer.
26/// - **precision**: following a `.`, the minimum number of digits, the absolute value being padded
27///   with leading zeros to reach it; a zero value formatted with a precision of 0 produces no
28///   digits at all. By default all necessary digits are printed.
29/// - **`Z`**: marks the argument as a multiple-precision integer (GMP's type character).
30/// - **conv**: the conversion — `d`, `i`, or `u` (decimal; unlike in C, all three are the same,
31///   and a negative value keeps its sign under every conversion), `o` (octal), or `x`/`X`
32///   (lowercase/uppercase hexadecimal).
33///
34/// A negative value is written as a `-` followed by the absolute value's digits, under every
35/// conversion; with the `#` flag the sign precedes the base prefix, as in `-0xff`.
36///
37/// Returns [`None`] when the format string is not a single well-formed `%Z` integer conversion: for
38/// instance if it uses `*` for the width or precision (which would need an integer argument that
39/// this single-value entry point does not supply), contains no `%Z` conversion or more than one,
40/// contains a conversion of any other type, or requests a width or precision beyond `i32::MAX` (the
41/// range of the C `int` GMP itself stores them in).
42///
43/// # Worst-case complexity
44/// $T(n) = O(n (\log n)^2 \log\log n)$
45///
46/// $M(n) = O(n \log n)$
47///
48/// where $T$ is time, $M$ is additional memory, and $n$ is `max(x.significant_bits(), p, w)`, with
49/// `p` and `w` the precision and field width requested by the format string.
50///
51/// # Examples
52/// ```
53/// use malachite_nz::integer::conversion::string::format_integer::format_integer_str;
54/// use malachite_nz::integer::Integer;
55///
56/// let x = Integer::from(-255);
57/// assert_eq!(format_integer_str(&x, "%Zd"), Some("-255".to_string()));
58/// assert_eq!(format_integer_str(&x, "%#Zx"), Some("-0xff".to_string()));
59/// assert_eq!(format_integer_str(&x, "%#ZX"), Some("-0XFF".to_string()));
60/// assert_eq!(format_integer_str(&x, "%#Zo"), Some("-0377".to_string()));
61/// assert_eq!(format_integer_str(&x, "%8Zd"), Some("    -255".to_string()));
62/// assert_eq!(
63///     format_integer_str(&x, "%08Zd"),
64///     Some("-0000255".to_string())
65/// );
66/// assert_eq!(format_integer_str(&x, "%.6Zd"), Some("-000255".to_string()));
67///
68/// let x = Integer::from(255);
69/// assert_eq!(format_integer_str(&x, "%+Zd"), Some("+255".to_string()));
70/// assert_eq!(format_integer_str(&x, "% Zd"), Some(" 255".to_string()));
71/// assert_eq!(
72///     format_integer_str(&x, "x = %Zd!"),
73///     Some("x = 255!".to_string())
74/// );
75///
76/// // invalid or unsupported format strings
77/// assert_eq!(format_integer_str(&x, "%d"), None);
78/// assert_eq!(format_integer_str(&x, "%*Zd"), None);
79/// assert_eq!(format_integer_str(&x, "%Zd %Zd"), None);
80/// ```
81///
82/// This is `gmp_snprintf` from `printf/snprintf.c`, GMP 6.3.0, where the format string contains a
83/// single `%Z` integer conversion and the buffer is always large enough.
84#[inline]
85pub fn format_integer_str(x: &Integer, fmt: &str) -> Option<String> {
86    format_gmp_str(*x < 0, x.unsigned_abs_ref(), None, b'Z', fmt)
87}
88
89impl GmpFormatArg for Integer {
90    /// Formats an [`Integer`] according to a single parsed conversion specification, which must be
91    /// a `%Z` integer conversion; see
92    /// [`gmp_format`](malachite_base::strings::gmp_format::gmp_format) and [`format_integer_str`].
93    ///
94    /// # Worst-case complexity
95    /// $T(n) = O(n (\log n)^2 \log\log n)$
96    ///
97    /// $M(n) = O(n \log n)$
98    ///
99    /// where $T$ is time, $M$ is additional memory, and $n$ is `max(self.significant_bits(), p,
100    /// w)`, with `p` and `w` the precision and field width of the specification.
101    ///
102    /// # Examples
103    /// ```
104    /// use malachite_base::gmp_format;
105    /// use malachite_nz::integer::Integer;
106    ///
107    /// assert_eq!(
108    ///     gmp_format!("%+Zd and %#Zx", Integer::from(255), Integer::from(-255)),
109    ///     Some("+255 and -0xff".to_string())
110    /// );
111    /// ```
112    fn gmp_format(&self, spec: &GmpConversionSpec) -> Option<String> {
113        if spec.type_chr != b'Z' {
114            return None;
115        }
116        format_gmp_integer_spec(*self < 0, self.unsigned_abs_ref(), None, spec)
117    }
118}