////
Copyright (c) 2024 The C++ Alliance, Inc. (https://cppalliance.org)

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

Official repository: https://github.com/boostorg/website-v2-docs
////
= Numbers
:navtitle: Numbers

== FAQ

. *Are there any Boost libraries that extend floating point precision, and at what cost?*
+
In pass:[C++], the precision of `float` and `double` types is determined by the IEEE 754 standard for floating-point arithmetic, which is used by nearly all modern compilers and hardware. A `float` (with 24 significant bits) is accurate to about 6 or 7 decimal places, a `double` (53 significant bits) to 15 to 17 decimal digits. A long double (80+ significant bits) extends this to 18 to 21 decimal digits. 
+
Boost does not replace these types, but extends your range of options using boost:multiprecision[]. There are the predefined types `cpp_dec_float_50` and `cpp_dec_float_100`, and the unlimited type `cpp_dec_float_<N>`, where you decide the value of N. `cpp_dec_float_50` would obviously give 50 digits, and `cpp_dec_float_201` gives 201 digits. For example:
+
[source,cpp]
----
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>

using namespace boost::multiprecision;

int main() {
    cpp_dec_float_50 pi("3.14159265358979323846264338327950288419716939937510");
    auto result = pi * pi;

    // Set the precision slightly higher than the number of digits
    std::cout << std::setprecision(51) << result << std::endl;
}

----
+
[source,cpp]
----
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <iostream>

// Let's define a type to take pi to 200 decimal places, 201 including the initial "3"
using cpp_dec_float_201 = boost::multiprecision::number<boost::multiprecision::cpp_dec_float<201> >;

int main() {

    cpp_dec_float_201 pi("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679"
        "8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196");

    std::cout << std::setprecision(201) << pi << std::endl;
}

----
+
boost:math[] adds high-quality special functions that integrate well with these types from boost:multiprecision[]. For example, `boost::math::gamma`, `boost::math::exp`, and `boost::math::lgamma` are available. Also, boost:qvm[] (quaternions, vectors, matrices) supports these custom precision types.
+
The cost as you can imagine is performance, the benefit is extreme accuracy. Under the hood, `cpp_dec_float<N>` stores `N` decimal digits of precision, using a base-10 representation, and uses an array of limbs to manage arbitrary-length mantissas.

. *Is there a Boost library that can help me with numbers like infinity, or the imaginary number that is the square root of -1?*
+
Yes, there is support for `infinity`, `NaN` (Not a Number), and imaginary numbers through different libraries. boost:math[] includes constants and utilities for working with `infinity` and `NaN`, which are part of IEEE 754 floating-point standards.
+
[source,cpp]
----
#include <boost/math/constants/constants.hpp>
#include <limits>
#include <iostream>
#include <cmath>

int main() {
    double inf = std::numeric_limits<double>::infinity();
    double nan = std::numeric_limits<double>::quiet_NaN();

    std::cout << "Infinity: " << inf << "\n";
    std::cout << "NaN: " << nan << "\n";

    // Or to test for them:
    if (std::isinf(inf)) std::cout << "This is infinity!\n";
    if (std::isnan(nan)) std::cout << "This is NaN!\n";

}

----
+
The complex functions of boost:math[] support imaginary numbers, such as the square root of -1.
+
[source,cpp]
----
#include <boost/math/complex.hpp>
#include <iostream>

int main() {
    std::complex<double> i(0.0, 1.0);
    std::complex<double> result = std::sqrt(std::complex<double>(-1.0, 0.0));

    std::cout << "sqrt(-1) = " << result << "\n"; // outputs (0,1)
}

----
+
boost:multiprecision[] supports high-precision complex types, for example:
+
[source,cpp]
----
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_complex.hpp>

using namespace boost::multiprecision;
using complex50 = cpp_complex_50;

int main() {
    complex50 c(0, 1);
    auto r = sqrt(complex50(-1, 0));
    std::cout << r << "\n";  // (0,1)
}

----

. *Can Boost.Multiprecision help calcuate a huge number of prime numbers?*
+
Use the type `boost::multiprecision::cpp_int` to safely store large prime numbers beyond the capacity of the standard `int64_t`, and the core algorithm known as the _Sieve of Eratosthenes_:
+
[source,cpp]
----
#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <chrono>

using boost::multiprecision::cpp_int;

std::vector<cpp_int> generate_primes(size_t count) {
    // Rough upper bound for nth prime using approximation: n * log(n) * 1.2
    size_t estimate = static_cast<size_t>(count * std::log(count) * 1.2);
    std::vector<bool> is_prime(estimate + 1, true);
    std::vector<cpp_int> primes;

    is_prime[0] = is_prime[1] = false;

    for (size_t i = 2; i <= estimate && primes.size() < count; ++i) {
        if (is_prime[i]) {
            primes.emplace_back(i); // Store as cpp_int
            for (size_t j = i * 2; j <= estimate; j += i) {
                is_prime[j] = false;
            }
        }
    }

    return primes;
}

int main() {
    size_t prime_count = 100000; // adjust this to your needs (10 million may need 6+ GB of RAM)
    
    auto start = std::chrono::high_resolution_clock::now();
    std::vector<cpp_int> primes = generate_primes(prime_count);
    auto end = std::chrono::high_resolution_clock::now();

    std::chrono::duration<double> elapsed = end - start;
    std::cout << "Generated " << primes.size() << " primes.\n";
    std::cout << "Largest prime found: " << primes.back() << "\n";
    std::cout << "Time elapsed: " << elapsed.count() << " seconds.\n";

    return 0;
}

----
+
Running this code:
+
[source,text]
----
Generated 100000 primes.
Largest prime found: 1299709
Time elapsed: 1.86556 seconds.

----

Note:: `cpp_int` is overkill for small primes, but essential if you're working with large ones, such as 512-bit cryptographic primes.

. *Am I right that Boost libraries do not improve on the performance of the standard floating point `double`?*
+
Correct. Use `double` if you can, and only use higher precision types when you're accumulating billions of values and errors grow unbounded, or you need more than 17 digits of accuracy, or you're solving numerically unstable equations, or you're doing astronomy, cryptography, quantum physics, symbolic algebra, or working with scientific constants.

Note:: A _numerically unstable equation_ is one in which small changes or errors in input, or intermediate calculations, can lead to large errors in the final result due to the amplification of rounding or truncation errors in floating-point arithmetic. Numerical instability often arises when subtracting two nearly equal numbers (called _catastrophic cancellation_), dividing by very small numbers, performing many iterations where small errors accumulate, and poor choice of algorithm. A catastrophic cancellation might occur when subtracting 1.0000001 from 1.0000002 - precision and rounding errors might distort the result. Stable algorithms preserve significant digits and give reliable results even with floating-point limits.

. *What scientific numbers, similar to pi, require precision beyond that provided by the standard `double`?*
+
Here is a table of the usual suspects:
+
[cols="2,1,3",options="header",stripes=even,frame=none]
|===
| *Constant* | *Typical Digits Needed* | *Why `double` Isn't Enough*
| π (pi) | 50-100+ | Needed with extreme accuracy in orbital mechanics, quantum computing, etc.
| e (Euler's number) | 30-100+ | Used in high-precision financial models, calculus, and exponential growth systems.
| γ (Euler-Mascheroni constant) | 50-100+ | Arises in analytic number theory and integrals.
| φ (Golden ratio) | 30+ | Used in precise design and algorithmic ratios.
| Planck's constant (h) | 25-100 | Central to quantum mechanics; precise modeling demands high precision.
| Fine-structure constant (α) | 30-80 | Key in atomic physics and fundamental interactions.
| Avogadro's number | 23+ | Often stored as a float, but high-accuracy simulations may demand higher precision.
| Speed of light (c) | 17+ | For ultra-precise relativistic calculations.
| Gravitational constant (G) | 20-100 | Known only to limited digits experimentally, but simulations may push precision.
| Riemann zeta constants | 30-200+ | Arise in number theory and string theory.
| Catalan's constant | 50+ | Appears in combinatorics and integrals.
| Apéry's constant | 50-200 | Arises in irrationality proofs and advanced analysis.
|===

Note:: Precision can become an obsession. Pi has been computed to over 100 trillion digits, but NASA's orbital calculations use only around the first 15 digits of pi (so a `double` would work!).

. *To avoid floating point numbers altogether, I could use fractions. For example, storing a third as 1 over 3 avoids using 0.33333 ad infinitum. Is there a Boost library that would make sense of numbers stored only as integer fractions?*
+
Yes. boost:rational[] is a library designed specifically to represent and manipulate rational numbers — that is, numbers stored as fractions of two integers (such as, 1/3, 355/113).
+
It avoids floating-point approximation entirely, preserving mathematical exactness throughout arithmetic operations. The library automatically normalizes (reduces) fractions - so 3/6 would be reduced to 1/2. And it can interoperate with `int`, `long`, or even `boost::multiprecision::cpp_int`. For example:
+
[source,cpp]
----
#include <boost/rational.hpp>
#include <iostream>

int main() {
    boost::rational<int> a(1, 3);  // 1/3
    boost::rational<int> b(2, 5);  // 2/5

    auto sum = a + b;              // 1/3 + 2/5 = 11/15
    auto product = a * b;          // 1/3 * 2/5 = 2/15

    std::cout << "Sum: " << sum.numerator() << "/" << sum.denominator() << "\n";
    std::cout << "Product: " << product << "\n";  // prints as 2/15

    // Comparison
    if (a < b)
        std::cout << "a is less than b\n";
}

----

Note:: Using rational numbers there is a risk of integer overflow, so consider using large integers for inputs (`boost::multiprecision::cpp_int` or similar), and this approach is not ideal for numbers known to be irrational (square root of 2, and the scientific constants listed above).

. *Can I use Boost.Multiprecision or Boost.Math to help with my project on RSA public-key encryption?*
+
Yes. Starting with the basic algorithm for RSA (Rivest-Shamir-Adleman - the authors of the algorithm) which follows these steps:
+
.. Choose two large prime numbers `p` and `q`
.. Compute `n = p * q`
.. Compute Euler's totient `ϕ(n) = (p-1)(q-1)`
.. Choose public exponent `e` such that `1 < e < ϕ(n)` and `gcd(e,ϕ(n)) = 1`
.. Compute private exponent `d` such that `e⋅d ≡ 1 mod ϕ(n)`
.. Now you have: `public-key = (e,n)` and `private-key = (d,n)`
+
We can now use `cpp_int` from boost:multiprecision[] to handle arbitrary-precision integers. And, if need be, you can use `is_prime` from boost:math[] for primality checks on larger randomly generated values (which you may want to add at a later date, using boost:random[]).
+
[source,cpp]
----
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/integer/common_factor_rt.hpp>
#include <iostream>

using namespace boost::multiprecision;

// Compute modular inverse of a modulo m using Extended Euclidean Algorithm
cpp_int modinv(cpp_int a, cpp_int m) {
    cpp_int m0 = m, t, q;
    cpp_int x0 = 0, x1 = 1;

    while (a > 1) {
        q = a / m;
        t = m;
        m = a % m;
        a = t;
        t = x0;
        x0 = x1 - q * x0;
        x1 = t;
    }

    return (x1 < 0) ? x1 + m0 : x1;
}

int main() {
    // Small primes for demo
    cpp_int p = 61;
    cpp_int q = 53;

    cpp_int n = p * q;                     // n = 3233
    cpp_int phi = (p - 1) * (q - 1);       // φ(n) = 3120

    cpp_int e = 17; // Common public exponent
    cpp_int d = modinv(e, phi);            // Compute private key

    // Display keys
    std::cout << "Public Key (e, n): (" << e << ", " << n << ")\n";
    std::cout << "Private Key (d, n): (" << d << ", " << n << ")\n";

    // Create message
    cpp_int message = 65;
    std::cout << "Initial message: " << message << "\n";

    // Encrypt message
    cpp_int encrypted = powm(message, e, n); // m^e mod n
    std::cout << "Encrypted message: " << encrypted << "\n";

    // Decrypt message
    cpp_int decrypted = powm(encrypted, d, n); // c^d mod n
    std::cout << "Decrypted message: " << decrypted << "\n";

    return 0;
}

----

Note:: Consider using `independent_bits_engine` from boost:random[] for a clean way to get large random integers of fixed bit-width, and then consider very large prime numbers of perhaps 1024 bits.

. *What does a 1024-bit prime number look like?*
+
Here is one:
+
[source,cpp]
----
cpp_int prime = 165918700393058288029118516503856682928352034064210292320510526037152431960844672521054555721941412725769027652540094762345484278576411078143188748708281181119556988860248537167684663864334811189453410905241474311369868568296877192226227785240656833746573473244854528133231976802973699288063056142727481235873

----

. *I need efficient memory storage for a real-time simulation. The use case is small counts, for example the number of carrier task forces operating in one ocean at any one time - a number which will never exceed 10 let alone 256?*
+
A full sized int or even a byte is comically oversized for this use case. You could use the standard: `std::uint8_t num_groups;`, however you might like to add some range-safety. Consider the following code as it's memory footprint is exacty 3 bytes:
+
[source,cpp]
----
#include <cstdint>
#include <boost/endian/arithmetic.hpp>
#include <boost/numeric/conversion/cast.hpp>

struct OceanStatus {
    boost::endian::little_uint8_t carrier_groups; // 1 byte on disk
    boost::endian::little_uint8_t submarine_groups;
    boost::endian::little_uint8_t air_wings;

    void set_carrier_groups(int x) {

        // Guarantee 0–10 range at runtime
        if (x < 0 || x > 10)
            throw std::out_of_range("carrier group count must be 0-10");

        // Safe cast to uint8_t
        carrier_groups = boost::numeric_cast<std::uint8_t>(x);
    }
};

----

. *What library should I look at to help pack really small integers (say 2 to 4 bits each) into the minimum number of bytes possible?*
+
The libary to evaluate is boost:dynamic-bitset[], it gives you stable bit indexing, easy clearing/writing slices, guaranteed predictable ordering, and portability across CPU architectures. In particular, check out the function `to_block_range` for exporting packed blocks. This should be easier - and safer - than hand-rolling your own masks and shifts.

. *How does an enormous physical value, such as the number of atoms in the observable Universe, compare with the maximum value current integers can hold?*
+
There are approximately 10 to the power of 80 hydrogen atoms in the observable Universe (hydrogen is by far the simplest and most common atom). Integers are rarely used in astronmical calculations (a `double` is most definitely the current unit of choice). However, it is interesting to imagine what type of integer would be needed for astronomical counting:
+
[cols="1,1",stripes=even,options="header",frame=none]
|===
| Bits | Approx Maximum Value - 10 to the power of
|   64 |  19 
|  128 |  38 
|  256 |  77 
|  512 | 154 
| 1024 | 308 
|===
+
That last row is remarkable because 10 to the power of 308 is roughly the same order of magnitude as the largest finite `double`. In other words, a 1024-bit integer can represent exact whole numbers all the way up to the largest magnitude that a `double` can represent, approximately. It's an illustration of the difference between _range_ and _precision_: floating-point numbers trade exactness for a huge dynamic range, while big integers remain precise at every value they can represent.
+
The main reason to go larger isn't astronomy — it's mathematics and cryptography, where the size of the numbers is part of the problem itself, not just a way of representing physical quantities.

. *What applications areas, realistically, will benefit from 128-bit integers?*
+
It would benefit many engineering application areas that require the exactness of integers:
+
* nanosecond timestamps spanning billions of years
* globally unique identifiers
* huge file offsets
* hash values
* financial calculations
* astronomy metadata
* simulation counters
+
Both GCC and Clang already support __int128 on many 64-bit platforms as a compiler extension.

. *Is there any realistic bounding limit to the size of numbers that mathematicians might be interested in?*
+
It seems there is no limit. Projects searching for huge prime numbers (super valuable in crypotgraph) routinely use integers with millions or even hundreds of millions of bits. The largest known prime number currently has tens of millions of decimal digits, requiring roughly 136 million bits to store.

. *Am I right that intermediate values often push the boundaries of size, rather than the results?*
+
Yes, very much so. Take the calculation `(a * b)/ c` : the size required to store `a * b` can skyrocket, before the division reduces it.

. *Are there any Boost libraries that might help me with my work on Feynman integrals?*
+
Unlike the integrals encountered in introductory calculus, Feynman integrals are often multidimensional (typically 4, 8, 12, or more dimensions), highly oscillatory, divergent unless carefully regularized, and dependent on many physical parameters such as masses and energies.
+
Accurate evaluation of Feynman integrals is essential for making precise predictions in particle physics. For example, the extraordinarily accurate theoretical prediction of the electron's magnetic moment (the g−2 value) required evaluating thousands of high-order Feynman diagrams, many containing extremely challenging integrals. The agreement between these calculations and experiment is among the most precise tests of any physical theory.
+
boost:graph[] should help with the topology of the diagrams, then boost:multiprecision[] for high-precision arithmetic, boost:math[] for special functions, then perhaps boost:random[] and boost:accumulators[] for Monte Carlo integration and statistical error measurements. Your project architecture might look like:
+
[source,text]
----
           Diagram Generator
                  │
                  ▼
           Boost.Graph
                  │
                  ▼
        Build Mathematical Model
                  │
                  ▼
       Boost.Math / Multiprecision
                  │
        ┌─────────┴─────────┐
        ▼                   ▼
 Analytical Solver    Monte Carlo Solver
                            │
                            ▼
                     Boost.Random
                            │
                            ▼
                 Boost.Accumulators
                            │
                            ▼
                  Numerical Results

----
+
And you might consider boost:numeric/ublas[] if solving systems of equations is in the mix.


== See Also

* xref:faq.adoc[]