Build and Usage

This page describes how to build beman.big_int, install it, and integrate it into an existing codebase with CMake. Once integrated, beman::big_int behaves very much like a built-in integral type: it works seamlessly with detailed algorithms, built-in types, and visualization features such as output-streaming and string representation, while efficient move-semantics keep intuitive, easy-to-read code performant. For full runnable programs that exercise these features, see Examples.

Dependencies

This project requires at least the following to build:

  • A C++ compiler that conforms to the C++23 standard or greater

  • CMake 3.30 or later

  • (Test only) GoogleTest

You can disable building tests by setting the CMake option BEMAN_BIG_INT_BUILD_TESTS to OFF when configuring the project.

Tested platforms

These are not necessarily the only supported platforms, but these are the ones that we test.

Compiler Version C++ Standards Standard Library

GCC

>= 14

>= C++23

libstdc++

Clang

>= 19

>= C++23

libstdc++, libc++

AppleClang

latest

>= C++23

libc++

MSVC

latest

>= C++23

MSVC STL

Building

You can build beman.big_int using a CMake workflow preset:

cmake --workflow --preset gcc-release

To list the available workflow presets, you can invoke:

cmake --list-presets=workflow

For details on building beman.big_int without using a CMake preset, refer to the Contributing Guidelines.

Optional: SIMD-accelerated multiplication

By default, multiplication of very large integers uses an exact integer number-theoretic transform for its FFT tier. This is correct on every conforming compiler and imposes no special build requirements.

A faster double-precision floating-point transform with hand-written SIMD kernels (ARM NEON, x86-64 AVX2, selected at runtime) is available behind the CMake option BEMAN_BIG_INT_SIMD_MUL (default OFF):

cmake --preset gcc-release -DBEMAN_BIG_INT_SIMD_MUL=ON

The option combines with the x86-64 kernel options below. Together with the AVX-512 IFMA kernels it is the fastest configuration measured on x86-64 (see the benchmarks): it only changes products large enough for the FFT, which it makes up to about 1.8x faster.

The SIMD path is exact only under the default IEEE round-to-nearest mode with no fast-math and no floating-point contraction. When built with this project’s CMake, the required flags (-ffp-contract=off -fno-fast-math, or /fp:strict on MSVC) are applied to the relevant translation units automatically. If you build with a different build system you must guarantee that floating-point environment yourself, or results may be silently incorrect. The default integer path has no such requirement.

Optional: x86-64 BMI2/ADX schoolbook kernels

On x86-64, the schoolbook multiply and square kernels are always built in two forms: a generic baseline and one using mulx (BMI2) and adcx/adox (ADX). Which one the library calls is chosen at compile time, with no runtime dispatch. When you compile with flags that define both BMI2 and ADX (for example -march=native, or -march=broadwell or newer, or -march=znver1 or newer), the BMI2/ADX kernels are selected automatically.

You can also select explicitly with the CMake option BEMAN_BIG_INT_X86_64_BMI2_ADX (default AUTO, also accepting ON/OFF):

cmake --preset gcc-release -DBEMAN_BIG_INT_X86_64_BMI2_ADX=ON

ON always builds a binary that requires a CPU with both BMI2 and ADX (Intel Broadwell or newer, AMD Zen or newer): running it on an older CPU, or an AUTO build compiled with -march= flags targeting a newer CPU than it then runs on, crashes with an illegal instruction. MSVC needs ON to opt in, since it never defines BMI2 or ADX itself. x64 emulation on Windows on ARM reports these features but may not execute them.

AVX2 was evaluated for these schoolbook kernels and is not used directly: a 64-bit-limb widening multiply needs a 64x64→128-bit product, which AVX2 has no vector instruction for. AVX-512 IFMA does provide one (see below), at a reduced 52-bit radix.

Optional: x86-64 AVX-512 IFMA kernels

On x86-64, a third schoolbook multiply/square kernel pair is available, built using AVX-512 IFMA (vpmadd52luq/vpmadd52huq, a vectorized 52x52→104-bit multiply-add at radix 2^52) together with BMI2/ADX. Selection is still compile-time, with no runtime dispatch cost, but it adds a size gate on top of the BMI2/ADX-or-generic choice above: operands below the gate still go through the BMI2/ADX-or-generic kernel, since the IFMA kernel only wins on larger operands. The multiply gate is shape-aware: the IFMA kernel is never taken below 6 limbs of the shorter operand and always from 20 limbs; in between it is taken once the product of the two lengths reaches 900 (shorter operand 6 to 7 limbs), 450 (8 to 12 limbs) or 330 (13 to 19 limbs), because its cost follows the shorter operand while the BMI2/ADX kernel’s follows the whole product. Squaring uses the IFMA kernel from 16 limbs, natively up to 256 limbs. Because the IFMA kernels move the schoolbook crossover, IFMA builds also use their own Karatsuba and Toom thresholds (see the crossover table in the benchmarks).

When you compile with flags that define AVX512IFMA, AVX512VL, AVX512BW, AVX512VBMI, BMI2, and ADX (for example -march=native, or -march=icelake-client, -march=rocketlake, -march=sapphirerapids, -march=znver4, or newer), the IFMA kernels are selected automatically for operands at or above the size gate.

You can also select explicitly with the CMake option BEMAN_BIG_INT_X86_64_AVX512_IFMA (default AUTO, also accepting ON/OFF):

cmake --preset gcc-release -DBEMAN_BIG_INT_X86_64_AVX512_IFMA=ON

ON always builds a binary that requires a CPU with AVX-512 F, BW, VL, VBMI, and IFMA, plus BMI2 and ADX (Intel Ice Lake, Tiger Lake, Rocket Lake, Sapphire Rapids, or newer; AMD Zen 4 or newer): running it on an older CPU, or an AUTO build compiled with -march= flags targeting a newer CPU than it then runs on, crashes with an illegal instruction. MSVC needs ON to opt in, since it never defines any of those feature macros itself. When ON is set and BEMAN_BIG_INT_X86_64_BMI2_ADX is left at its default AUTO, the latter is promoted to ON too: the IFMA kernel’s below-the-gate fallback is the BMI2/ADX kernel, and MSVC cannot auto-detect it either. Set BEMAN_BIG_INT_X86_64_BMI2_ADX=OFF explicitly if you want that fallback to stay the generic kernel instead.

Optional: the C++ named module

beman.big_int can also be consumed as the named module beman.big_int, behind the CMake option BEMAN_BIG_INT_BUILD_MODULE (default OFF):

cmake --workflow --preset llvm-release-module

This needs a newer toolchain than the header build, and is not available with AppleClang. See C++ Named Modules for the full story.

Optional: a custom namespace

Everything is declared in beman::big_int by default. A project that wraps the library can move it into a namespace of its own with the CMake option BEMAN_BIG_INT_NAMESPACE, which takes a qualified namespace name:

cmake --preset gcc-release -DBEMAN_BIG_INT_NAMESPACE=mylib::big_int

The library is partly compiled, so its sources and every consumer must agree on the namespace. CMake propagates a non-default value to consumers of the beman::big_int target. Without CMake, define the BEMAN_BIG_INT_NAMESPACE macro to the same value everywhere, both for the sources under src/ and for every translation unit that includes the headers. The *-release-namespace presets build and run the whole test suite with BEMAN_BIG_INT_NAMESPACE=wrapper::nested::big_int. The examples use beman::big_int by name, so they are skipped with any other namespace.

The library declares its own detail and literals namespaces inside the chosen namespace, so pick one that another library does not already populate. boost::multiprecision currently collides with Boost.Multiprecision itself: both define detail::make_signed and detail::make_unsigned, and Boost’s literals namespace is not inline while this library’s is.

Installation

To install beman.big_int globally after building with the gcc-release preset, you can run:

sudo cmake --install build/gcc-release

Alternatively, to install to a prefix, for example /opt/beman, you can run:

sudo cmake --install build/gcc-release --prefix /opt/beman

This will generate the following directory structure:

/opt/beman
|-- include/
|   \-- beman/
|       \-- big_int/
|           |-- big_int.hpp
|           \-- ...
\-- lib/
    \-- cmake/
        \-- beman.big_int/
            |-- beman.big_int-config-version.cmake
            |-- beman.big_int-config.cmake
            \-- beman.big_int-targets.cmake

CMake configuration

If you installed beman.big_int to a prefix, you can specify that prefix to your CMake project using CMAKE_PREFIX_PATH; for example, -DCMAKE_PREFIX_PATH=/opt/beman.

You need to bring in the beman.big_int package to define the beman::big_int CMake target:

find_package(beman.big_int REQUIRED)

You will then need to add beman::big_int to the link libraries of any libraries or executables that include beman.big_int headers:

target_link_libraries(yourlib PUBLIC beman::big_int)

Using beman.big_int

To use beman.big_int in your C++ project, include an appropriate beman.big_int header from your source code:

#include <beman/big_int.hpp>

beman.big_int headers are to be included with the beman/big_int/ prefix. Altering include search paths to spell the include target another way (e.g. #include <big_int.hpp>) is unsupported.

To consume beman.big_int as a named module instead, import beman.big_int; and link beman::big_int_module in place of beman::big_int. See C++ Named Modules.

The straightforward program below shows usage of beman.big_int. It computes and verifies the value of 100! in its full, pure-integral, non-truncated form:

#include <beman/big_int.hpp>

#include <iomanip>
#include <iostream>

template <class BigIntType>
constexpr auto factorial(unsigned int n) -> BigIntType {
    return (n <= 1) ? 1 : n * factorial<BigIntType>(n - 1);
}

auto main() -> int {
    using beman::big_int::big_int;

    // Compute the 100th Factorial number.
    const big_int fact_100{factorial<big_int>(100)};

    using namespace beman::big_int::literals;

    const big_int bn_ctrl{
        93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000_n};

    const bool result_is_ok{fact_100 == bn_ctrl};

    std::cout << "fact_100:\n"
              << to_string(fact_100) << "\n\nresult_is_ok: " << std::boolalpha << result_is_ok << std::endl;

    return result_is_ok ? 0 : -1;
}