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.
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 ( |
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
|
|
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
|
|
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 |
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>
|
|
|
To consume |
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;
}