Columns


Standard C/C++

P. J. Plauger

The Header <limits>

The Standard C library has provided <limits.h> for many years. The draft Standard C++ library now offers a more structured, and extensible, alternative using templates.


Introduction

The header <limits> is another of those "odds and ends" that defy categorization in the draft Standard C++ library. A fairly recent invention of the C++ standards committees, its official job is to define the template class numeric_limits, but in reality that is its least important function. Its real purpose is to define a host of explicit specializations for this template class. Each of these explicit specializations describes one of the basic arithmetic types of the C++ language, probably in more detail than you'll ever care to know.

Moreover, like other facilities defined in this library, the template class numeric_limits is intended to serve as a prototype for explicit specializations defined by the programmer. The idea is that programmers will write reusable code whose behavior can adapt to the specific properties of some user-supplied type T. They will do so by making inquiries of numeric_limits<T>. If you, the (re)user, want to specify a type T other than one of the basic arithmetic types, you must supply an explicit specialization that gives the appropriate answers. The template itself is designed to give only discouraging answers.

The obvious precursor to the C++ header <limits> is the (much) older C header <limits.h>. As I recall, <limits.h> was developed by the folks standardizing Posix in the early 1980s, before the name Posix was even coined. Its purpose was to capture in one place the properties of the various integer types, which are notoriously elastic among implementations of C.

<limits.h> consists of nothing but macros that expand to values suitable for use in #if expressions. Thus, you can write preprocessing directives that selectively include different chunks of code depending on these integer properties. With just a little trickery, you can determine whether or not integer arithmetic is twos complement, or whether plain char has a signed representation. Sometimes this matters, particularly for code designed to be highly robust and portable.

Committee X3J11 (now simply J11) took over this header long about 1983, as part of a general hijacking of the C library specification. The committee supplemented <limits.h> with the newly invented header <float.h>. As the name implies, the latter header describes the properties of the three floating-point types, float, double, and long double. The contents of <float.h> were based heavily on the recommendations of representatives from Cray Research and other companies who care deeply about numerical programming in C.

Preprocessors don't do floating-point arithmetic, so many of the macros defined in <float.h> can't be used in #if expressions. Most cannot even be used as static initializers. An implementation can, for example, call a function to determine the minimum double value DBL_MIN, so you can't reliably write DBL_MIN in a static initializer.

Just where you can use information of this sort is critical. Making it available to the preprocessor is essential if you need to conditionally include different chunks of source code. Barring that, it's nice to have a testable parameter be a "constant integer expression." It can then serve as a case label value or an array size. Barring that, it's helpful if the parameter is a "constant expression," which can be used in a static initializer for some non-integer object.

General r-value expressions, such as DBL_MIN above, can only be determined by run-time evaluation; but they also support the definition of parameters that can change while the program executes. In IEEE floating-point arithmetic, for example, the rounding mode can be changed at run time, even though the current C Standard specifies no standard way to do so. So the macro FLT_ROUNDS expands to an integer r-value expression.

The parameters defined by template class numeric_limits are all template members. They have nothing to do with the preprocessor. Some are constant integer expressions, in the extended C++ sense — either nested enumeration constants or static const member objects. But many are member functions. The distinction is generally less important in C++ than in C, since you have far more latitude in writing static initializers. But sometimes you have to indulge in some pretty precious code to make use of numeric_limits.

Consider, for example, the following "stupid template trick:"

template<bool B>
struct checkif;
template<> struct checkif<true>
{};

This declares a template class checkif which takes a single Boolean template parameter. It supplies no definition of the template itself, just an explicit specialization for the parameter value true. So if you write:

checkif<
  numeric_limits<int>::is_signed>
    dummy0;

the translator quietly creates a worthless object called dummy0. But if you write:

checkif<numeric_limits<int>::min()
  == 0> dummy1;

you will get some kind of compile-time diagnostic because the template class is undefined. If you're lucky, the diagnostic will be traceable in some obvious way to the bogus predicate you intended to test for with this declaration.

In my experience, it takes a pretty careful coding style to use to advantage the contents of <limits.h> and <float.h>. Most programs don't have to be that portable. Many more can't be made portable simply by adding tests for value ranges, signedness, or similar properties. But these headers often give you a fighting chance if you want to try. The newer C++ header <limits> is another, more sophisticated, weapon in this arsenal.

Summary of numeric_limits

The template class numeric_limits is shown in Listing 1. It describes many arithmetic properties of its parameter type T. The header <limits> defines explicit specializations for the types:

wchar_t        bool         
char           signed char
unsigned char  short        
unsigned short int
unsigned int   long         
unsigned long  float
double         long double

For all these explicit specializations, the member is_specialized is true, and all relevant members have meaningful values. The program can supply additional explicit specializations.

For an arbitrary specialization, no members have meaningful values. A member object that does not have a meaningful value stores zero (or false) and a member function that does not return a meaningful value returns T(0). The members, in alphabetical order, are:

static T denorm_min() throw();

The function returns the minimum value for the type (which is the same as min() if has_denorm is false).

static const int digits = 0;

The member stores the number of radix digits that the type can represent without change (which is the number of bits other than any sign bit for a predefined integer type, or the number of mantissa digits for a predefined floating-point type).

static const int digits10 = 0;

The member stores the number of decimal digits that the type can represent without change.

static T epsilon() throw();

The function returns the difference between 1 and the smallest value greater than 1 that is representable for the type (which is the value FLT_EPSILON for type float).

static const bool has_denorm = false;

The member stores true for a floating-point type that has denormalized values (effectively a variable number of exponent bits).

static const bool has_denorm_loss = false;

The member stores true for a type that determines whether a value has lost accuracy because it is delivered as a denormalized result (too small to represent as a normalized value) or because it is inexact (not the same as a result not subject to limitations of exponent range and precision), an option with IEC 559 floating-point representations that can affect some results.

static const bool has_infinity = false;

The member stores true for a type that has a representation for positive infinity. True if is_iec559 is true.

static const bool has_quiet_NaN = false;

The member stores true for a type that has a representation for a quiet NaN, an encoding that is "Not a Number" which does not signal its presence in an expression. True if is_iec559 is true.

static const bool has_signaling_NaN = false;

The member stores true for a type that has a representation for a signaling NaN, an encoding that is "Not a Number" which signals its presence in an expression by reporting an exception. True if is_iec559 is true.

static T infinity() throw();

The function returns the representation of positive infinity for the type. The return value is meaningful only if has_infinity is true.

static const bool is_bounded = false;

The member stores true for a type that has a bounded set of representable values (which is the case for all predefined types).

static const bool is_exact = false;

The member stores true for a type that has exact representations for all its values (which is the case for all predefined integer types). A fixed-point or rational representation is also considered exact, but not a floating-point representation.

static const bool is_iec559 = false;

The member stores true for a type that has a representation conforming to IEC 559, an international standard for representing floating-point values (also known as IEEE 754 in the USA).

static const bool is_integer = false;

The member stores true for a type that has an integer representation (which is the case for all predefined integer types).

static const bool is_modulo = false;

The member stores true for a type that has a modulo representation, where all results are reduced modulo some value (which is the case for all predefined unsigned integer types).

static const bool is_signed = false;

The member stores true for a type that has a signed representation (which is the case for all predefined floating-point and signed integer types).

static const bool is_specialized = false;

The member stores true for a type that has an explicit specialization defined for template class numeric_limits (which is the case for all scalar types other than pointers).

static T max() throw();

The function returns the maximum finite value for the type (which is INT_MAX for type int and FLT_MAX for type float). The return value is meaningful if is_bounded is true.

static const int max_exponent = 0;

The member stores the maximum positive integer such that the type can represent as a finite value radix raised to that power (which is the value FLT_MAX_EXP for type float). Meaningful only for floating-point types.

static const int max_exponent10 = 0;

The member stores the maximum positive integer such that the type can represent as a finite value 10 raised to that power (which is the value FLT_MAX_10_EXP for type float). Meaningful only for floating-point types.

static T min() throw();

The function returns the minimum normalized value for the type (which is INT_MIN for type int and FLT_MIN for type float). The return value is meaningful if is_bounded is true or is_signed is false.

static const int min_exponent = 0;

The member stores the minimum negative integer such that the type can represent as a normalized value radix raised to that power (which is the value FLT_MIN_EXP for type float). Meaningful only for floating-point types.

static const int min_exponent10 = 0;

The member stores the minimum negative integer such that the type can represent as a normalized value 10 raised to that power (which is the value FLT_MIN_10_EXP for type float). Meaningful only for floating-point types.

static T quiet_NaN() throw();

The function returns a representation of a quiet NaN for the type. The return value is meaningful only if has_quiet_NaN is true.

static const int radix = 0;

The member stores the base of the representation for the type (which is 2 for the predefined integer types, and the base to which the exponent is raised, or FLT_RADIX, for the predefined floating-point types).

static T round_error() throw();

The function returns the maximum rounding error for the type.

enum float_round_style {
 round_indeterminate = -1, round_toward_zero = 0,
    round_to_nearest = 1,
    round_toward_infinity = 2,
    round_toward_neg_infinity = 3
    };
static const float_round_style round_style =
     round_toward_zero;

The member stores a value that describes the various methods that an implementation can choose for rounding a floating-point value to an integer value. You can guess what each rounding mode does from the name of its enumeration constant.

static T signaling_NaN() throw();

The function returns a representation of a signaling NaN for the type. The return value is meaningful only if has_signaling_NaN is true.

static const bool tinyness_before = false;

The member stores true for a type that determines whether a value is "tiny" (too small to represent as a normalized value) before rounding, an option with IEC 559 floating-point representations that can affect some results.

static const bool traps = false;

The member stores true for a type that generates some kind of signal to report certain arithmetic exceptions.

Implementing numeric_limits

Listing 2 shows one way to implement the header <limits>. I've written it largely in terms of a macro _STCONS, which chooses one of two styles for writing constant integer expressions. The proper way, in modern C++, is to write a static const member object with an initializer, as in:

static const T name = val;

But not all current compilers permit this form, since it's a fairly recent addition to the draft C++ Standard. To accommodate older compilers, you can write instead:

enum {name = val};

This gives name the proper value val, but says nothing about its type. It's a rare program that cares, but those that do can rightly object that this definition doesn't fully conform to the draft C++ Standard.

The code in this header is terribly repetitious, what with all the explicit specializations that are required. So I've factored out as much common code as possible into several common base classes:

I include all the repetitions here because each explicit specialization is just different enough from its brethren to be educational. It is also educational to figure out what some of the clever expressions do. I particularly enjoy the cryptic literal 301L, which is meaningful only to those who love logarithms.

But the basic goal of this presentation is to elucidate, not to obscure. Template class numeric_limits tells you more than you probably want to know about the basic arithmetic types in C++, as promised. It also serves as a useful guide for describing numeric classes that you define yourself. o

P.J. Plauger is Senior Editor of C/C++ Users Journal and President of Dinkumware, Ltd. He is the author of the Standard C++ Library shipped with Microsoft's Visual C++, v5.0. For eight years, he served as convener of the ISO C standards committee, WG14. He remains active on the C++ committee, WG21. His latest books are The Draft Standard C++ Library, Programming on Purpose (three volumes), and Standard C (with Jim Brodie), all published by Prentice-Hall. You can reach him at pjp@plauger.com.