std::numeric_limits Support

<beman/big_int/limits.hpp> specializes std::numeric_limits for every basic_big_int, so generic code can query the type the same way it queries int or double. Include it directly, or transitively through the umbrella <beman/big_int.hpp>.

basic_big_int is unbounded: it grows to fit its value, so it has no largest or smallest value for the specialization to report. Every value-returning member yields zero, is_bounded is false, and digits saturates at INT_MAX. These are the same answers Boost.Multiprecision gives for an unbounded boost::multiprecision::cpp_int, digit counts included, which makes the two interchangeable in code that reads these traits.

A zero from max() means there is no maximum, not the maximum is zero. Test is_bounded before reading any of the value-returning members. A template written for fixed-width integers that reaches straight for max() will silently compute against zero.

Synopsis

namespace std {

template <std::size_t b, class L, class A>
class numeric_limits<beman::big_int::basic_big_int<b, L, A>> {
    using type = beman::big_int::basic_big_int<b, L, A>;

  public:
    static constexpr bool is_specialized = true;
    static constexpr bool is_signed      = true;
    static constexpr bool is_integer     = true;
    static constexpr bool is_exact       = true;

    static constexpr bool               has_infinity      = false;
    static constexpr bool               has_quiet_NaN     = false;
    static constexpr bool               has_signaling_NaN = false;
    static constexpr float_denorm_style has_denorm        = denorm_absent;  // deprecated in C++23
    static constexpr bool               has_denorm_loss   = false;          // deprecated in C++23

    static constexpr float_round_style round_style = round_toward_zero;

    static constexpr bool is_iec559  = false;
    static constexpr bool is_bounded = false;
    static constexpr bool is_modulo  = false;

    static constexpr int digits       = INT_MAX;
    static constexpr int digits10     = /* see below */;
    static constexpr int max_digits10 = /* see below */;
    static constexpr int radix        = 2;

    static constexpr int min_exponent   = 0;
    static constexpr int min_exponent10 = 0;
    static constexpr int max_exponent   = 0;
    static constexpr int max_exponent10 = 0;

    static constexpr bool traps           = false;
    static constexpr bool tinyness_before = false;

    static constexpr type min() noexcept(/* see below */);
    static constexpr type lowest() noexcept(/* see below */);
    static constexpr type max() noexcept(/* see below */);
    static constexpr type epsilon() noexcept(/* see below */);
    static constexpr type round_error() noexcept(/* see below */);
    static constexpr type infinity() noexcept(/* see below */);
    static constexpr type quiet_NaN() noexcept(/* see below */);
    static constexpr type signaling_NaN() noexcept(/* see below */);
    static constexpr type denorm_min() noexcept(/* see below */);
};

} // namespace std

The <limits> forwarders for cv-qualified types apply as usual, so std::numeric_limits<const basic_big_int<…​>> reports the same values.

What each member reports

Member Value Notes

is_specialized

true

The specialization exists for every basic_big_int, whatever its inline capacity, limb type, or allocator — including pmr::big_int.

is_signed

true

basic_big_int is a signed type; the sign is held separately from the magnitude.

is_integer

true

-

is_exact

true

Every value is represented exactly; there is no rounding.

has_infinity, has_quiet_NaN, has_signaling_NaN

false

There are no such values.

has_denorm

denorm_absent

Deprecated in C++23; reported for completeness.

has_denorm_loss

false

Deprecated in C++23; reported for completeness.

round_style

round_toward_zero

Division truncates toward zero, matching div_rem_to_zero.

is_iec559

false

Not a floating-point type.

is_bounded

false

The set of representable values is not finite for any practical purpose. See below.

is_modulo

false

Arithmetic never wraps: an operation that would overflow a fixed-width type grows the representation instead.

digits

INT_MAX

The count of radix-2 digits is not fixed, so it saturates rather than reporting a width.

digits10

646456992

floor(log10(2) * (digits - 1)), the decimal digits that always survive a round trip.

max_digits10

646456994

floor(log10(2) * digits) + 2, enough decimal digits to tell any two values apart.

radix

2

The magnitude is a sequence of binary limbs.

min_exponent, min_exponent10, max_exponent, max_exponent10

0

Not a floating-point type.

traps

false

No operation traps.

tinyness_before

false

Not a floating-point type.

min(), lowest(), max()

0

There is no extreme value. See the note above.

epsilon(), round_error()

0

Not meaningful for an exact integer type.

infinity(), quiet_NaN(), signaling_NaN(), denorm_min()

0

No such value exists, so the default is returned.

The digit counts are derived from digits with the same expressions Boost.Multiprecision uses, down to the shared log10(2) constant, so the two libraries report identical numbers. The values quoted above are what those expressions produce wherever INT_MAX is 2147483647.

The noexcept specifications

Each value-returning member returns a default-constructed basic_big_int, and is noexcept exactly when that constructor is — that is, when the allocator’s default constructor is noexcept. This holds for big_int and for pmr::big_int, so in practice every member is noexcept; only a user-supplied allocator with a throwing default constructor makes it otherwise.

All members are usable in a constant expression.

Why is_bounded is false

A basic_big_int object does have a ceiling: its representation cannot exceed max_size() bits, reported per object by max_size and max_representation_size. That ceiling is a property of the representation rather than of the type, and it sits far beyond any allocation a program can make — more than 1011 bits on a 64-bit platform, which is why an allocation failure, not the limit, is what a program actually meets first.

Reporting it as max() would therefore be misleading twice over: it would promise a value no program can construct, and it would let a template silently treat basic_big_int as a fixed-width type. is_bounded == false says the useful thing instead, and matches what Boost.Multiprecision reports for cpp_int.

Using it in generic code

The specialization is there so that a template constrained on std::numeric_limits accepts basic_big_int alongside the built-in integers:

template <class T>
concept exact_integer = std::numeric_limits<T>::is_specialized &&
                        std::numeric_limits<T>::is_integer &&
                        std::numeric_limits<T>::is_exact;

static_assert(exact_integer<beman::big_int::big_int>);
static_assert(exact_integer<int>);
static_assert(!exact_integer<double>);

Code that needs a bound must branch on is_bounded:

template <class T>
std::string describe_range() {
    using lim = std::numeric_limits<T>;
    if constexpr (lim::is_bounded) {
        return std::to_string((lim::min)()) + " .. " + std::to_string((lim::max)());
    } else {
        return "unbounded";
    }
}

See the numeric limits example for a complete program.