FLINT3.7.0-dev
Algorithm

Radix conversion

Cost
O(M(n)log⁡n)O(M(n) \log n) for nn-limb numbers, where M(n)M(n) is the cost of multiplication.

Converting an integer from binary to decimal and back is needed for input and output, and for arithmetic in a decimal (or pp-adic) radix. The schoolbook methods (repeated division by the radix, or Horner's rule) take quadratic time, which is far too slow for numbers of millions of digits. The radix module uses a quasi-linear variant that avoids divisions entirely by doing the arithmetic in the target radix.

Throughout, β=2FLINT_BITS\beta = 2^{\mathtt{FLINT\_BITS}} is the binary limb radix and B=be<βB = b^e < \beta the target limb radix, for example B=1019B = 10^{19} for decimal arithmetic on a 64-bit machine. Converting an integer of nn limbs changes the number of limbs by the constant factor log⁡β/log⁡B\log \beta / \log B.

Quadratic methods#

From radix BB to binary, Horner's rule computes x=(⋯(an−1B+an−2)B+⋯ )B+a0x = (\cdots(a_{n-1} B + a_{n-2}) B + \cdots) B + a_0 with n−1n - 1 multiplications of a growing binary number by the word BB (radix_get_mpn_basecase). From binary to radix BB, repeated division by BB produces the limbs a0,a1,…a_0, a_1, \ldots as remainders (radix_set_mpn_basecase, which uses GMP's assembly mpn_divrem_1 while the number is long and a precomputed inverse of BB for the last dozen limbs). Both cost O(n2)O(n^2) and are used below 40 to 50 limbs.

Divide and conquer#

The classical subquadratic algorithm [Knu1997, §4.4; BZ2011, §1.7] converts from binary to radix BB by splitting x=qBk+rx = q B^k + r with 0≤r<Bk0 \le r < B^k, k≈n/2k \approx n/2, by one division by the precomputed power BkB^k, and converting qq and rr recursively; the conversion from radix BB to binary splits the digits instead and computes x=xhiBk+xlox = x_{\mathrm{hi}} B^k + x_{\mathrm{lo}} with one multiplication. With T(n)=2T(n/2)+O(M(n))T(n) = 2T(n/2) + O(M(n)), both cost O(M(n)log⁡n)O(M(n) \log n), but the division-based direction is several times slower in practice because a division costs a few multiplications.

The key observation of the method used by FLINT is that splitting is free in the source radix and combining needs only multiplication and addition, so it is enough to always split in the source radix and to combine in the target radix.

Algorithm 1: Conversion by multiplication in the target radix

Input: the digits a0,…,am−1a_0, \ldots, a_{m-1} of x=∑aicix = \sum a_i c^i in a source radix cc; a target radix BB with arithmetic on numbers written in radix BB; the powers ckc^{k}, written in radix BB, for the exponents kk used below.

Output: xx written in radix BB.

  1. If mm is small, evaluate by Horner's rule in radix-BB arithmetic.
  2. Choose k≈m/2k \approx m/2. Recursively convert xlo=∑i<kaicix_{\mathrm{lo}} = \sum_{i<k} a_i c^i and xhi=∑i≥kaici−kx_{\mathrm{hi}} = \sum_{i \ge k} a_i c^{i-k}.
  3. Return xlo+xhi⋅ckx_{\mathrm{lo}} + x_{\mathrm{hi}} \cdot c^k, computed in radix BB.

Theorem 1. Algorithm 1 is correct. If multiplication of nn-limb numbers in radix BB costs MB(n)M_B(n) with MB(n)/nM_B(n)/n nondecreasing, the conversion costs O(MB(n)log⁡n)O(M_B(n) \log n) for an nn-limb result, plus the cost of the powers.

Proof. Correctness is the identity x=xlo+xhickx = x_{\mathrm{lo}} + x_{\mathrm{hi}} c^k. The two halves have about half the size of xx in either radix, so the cost satisfies T(n)=2T(n/2)+O(MB(n))T(n) = 2T(n/2) + O(M_B(n)), whence T(n)=O(MB(n)log⁡n)T(n) = O(M_B(n) \log n).

To convert from radix BB to binary, take c=Bc = B and do the arithmetic in binary (radix_get_mpn). To convert from binary to radix BB, take c=2sc = 2^s with ss the largest integer such that 2s<B2^s < B (so s=63s = 63 for B=1019B = 10^{19}): reading the binary input in chunks of ss bits gives its digits in radix cc at no cost, and the arithmetic is done in radix BB with radix_mul, which uses the small-prime FFT and propagates the carries in radix BB (radix_set_mpn). In either direction no division by a large number is needed.

The table of powers#

The recursion uses the powers ck0,ck1,…c^{k_0}, c^{k_1}, \ldots with k0=⌈m/2⌉k_0 = \lceil m/2 \rceil and ki+1=⌈ki/2⌉k_{i+1} = \lceil k_i / 2 \rceil down to k=1k = 1. At every node the split is made at the largest kik_i that is at most half the length of the current piece, so that the recursion stays balanced and only these O(log⁡m)O(\log m) powers are needed.

Lemma 2. ki=2ki+1k_i = 2 k_{i+1} or ki=2ki+1−1k_i = 2 k_{i+1} - 1. Hence ckic^{k_i} is either the square of cki+1c^{k_{i+1}} or that square divided exactly by cc.

Proof. ki+1=⌈ki/2⌉k_{i+1} = \lceil k_i/2 \rceil is ki/2k_i/2 for even kik_i and (ki+1)/2(k_i + 1)/2 for odd kik_i.

The table therefore costs one squaring and possibly one exact division by a single word per level, O(MB(n))O(M_B(n)) in total. In binary, a power of B=1019=219⋅519B = 10^{19} = 2^{19} \cdot 5^{19} is divisible by 219k2^{19k}, so the binary powers BkB^k end in many zero limbs; these are recorded and skipped in the multiplications. Since the table depends only on the size of the input, radix_set_mpn keeps the most recent one in a per-thread cache, which speeds up the conversion of many numbers of similar size, as when printing a vector.

Decimal strings#

Once a number is in radix 101910^{19}, writing its decimal digits takes linear time: each limb is split into groups of digits with a few divisions by constants, and pairs of digits are copied from a 200-byte table (radix_get_str_decimal). Accordingly:

  • flint_mpn_get_str and fmpz_get_str in base 10 convert with radix_set_mpn and radix_get_str_decimal when fft_small is available, except for numbers of 40 to 1400 limbs, where GMP's mpz_get_str (a division-based subquadratic algorithm with fast basecases) is currently faster; other bases use GMP.
  • fmpz_set_str in base 10 parses short strings with GMP's mpn_set_str and longer ones (from 24000 digits) by divide and conquer over the string, combining the halves as xhi⋅10k+xlo=(xhi⋅5k)2k+xlox_{\mathrm{hi}} \cdot 10^k + x_{\mathrm{lo}} = (x_{\mathrm{hi}} \cdot 5^k) 2^k + x_{\mathrm{lo}} with precomputed powers of 5, in parallel when threads are available.

Above 500 limbs, the two halves of the recursion of radix_get_mpn and radix_set_mpn are converted in parallel when threads are available, leaving the remaining threads to the multithreaded multiplication near the root.

Use in FLINT#

radix_get_mpn and radix_set_mpn (with the divide and conquer versions radix_get_mpn_divconquer and radix_set_mpn_divconquer), radix_get_str_decimal, flint_mpn_get_str, and through them fmpz_get_str and the conversions between fmpz_t and radix_integer_t (radix_integer_set_fmpz, radix_integer_get_fmpz); the decimal module builds on the radix arithmetic. See also Converting between radix B and binary.

slong radix_get_mpn(nn_ptr res, nn_srcptr a, slong an, const radix_t radix)
slong radix_set_mpn(nn_ptr res, nn_srcptr a, slong an, const radix_t radix)
char * radix_get_str_decimal(char * res, nn_srcptr x, slong n, int negative, const radix_t radix)
char * flint_mpn_get_str(char * res, int base, mp_srcptr x, mp_size_t xn, int negative)

Referenced from#

References#

  • [Knu1997] Knuth, D. E. The Art of Computer Programming, volume 2: Seminumerical algorithms, 1997
  • [BZ2011] R. P. Brent and P. Zimmermann, Modern Computer Arithmetic, Cambridge University Press (2011), http://www.loria.fr/~zimmerma/mca/pub226.html
Cite this page

The FLINT team, “Radix conversion,” FLINT: Fast Library for Number Theory, documentation, version 3.7.0-dev, 2026. https://flintlib.org/doc/algorithms/integers/radix-conversion.html (generated 2026-11-02).