________________________________________________________________________ P1370R0: GENERIC NUMERICAL ALGORITHM DEVELOPMENT WITH(OUT) `NUMERIC_LIMITS' Mark Hoemmen (mhoemme@sandia.gov) and Damien Lebrun-Grandie (qdi@ornl.gov) ________________________________________________________________________ 21 Nov 2018 Table of Contents _________________ 1 Proposal 2 Terms 3 LAPACK's numeric "traits" and safe minimum .. 3.1 LAPACK is a library of generic numerical algorithms 4 Conclusion 5 Funding and disclaimer 1 Proposal ========== P0437R1 proposes to "[b]reak the monolithic `numeric_limits' class template apart into a lot of individual trait templates, so that new traits can be added easily." We find this uncontroversial, and agree that use of a trait should require (in the SFINAE sense) that the trait makes sense for the template argument. For example, the "maximum finite value" of an implementation-specific arbitrary-precision floating-point type could be unbounded per instance of the type, as in MPFR[1], or it could depend on a run-time option, as in ARPREC.[2] Creating new traits also gives us the opportunity to fix the inconsistent definition of `numeric_limits::min' for integer vs. floating-point types `T'. For integer types, this function returns the minimum value, which for signed types is the negative value of largest magnitude. However, for floating-point types, it returns the smallest positive (!) value. This is surprising, and can lead to errors when writing algorithms meant for either integers or floating-point values. Nevertheless, the actual value is useful in practice for writing generic numerical algorithms. We propose the following: 1. Whatever trait replaces `numeric_limits::min', should always give the minimum finite value of `T'. For floating-point types, it should give the same value as `numeric_limits::lowest()' does now. 2. The actual current value of `numeric_limits::min()' for floating-point types `T' is useful for writing generic numerical algorithms, but the name "min" is confusing. We propose bikeshedding a different name, and suggest `safe_minimum_v', based on the equivalent value in the LAPACK linear algebra library. 2 Terms ======= /Numerical algorithms/ use floating-point numbers as approximations to real numbers, to do the kinds of computations that scientists, engineers, and statisticians often find useful. /Generic numerical algorithms/ are written to work for different kinds of floating-point number types. /IEEE 754/ is the Institute of Electrical and Electronics Engineers' standard for binary floating-point computation. The standard first came out in 1985, and the latest revision was released in 2008. (The IEEE 754 Floating Point Standards Committee approved a new draft on 20 July 2018, as reported by Committee member James Demmel over e-mail.) 3 LAPACK's numeric "traits" and safe minimum ============================================ In this section, we will show that while `numeric_limits::min' is not informatively named for floating-point types, it is still a useful constant for writing generic numerical algorithms. In fact, the LAPACK[3] linear algebra library uses this value. 3.1 LAPACK is a library of generic numerical algorithms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LAPACK is a Fortran library, but it takes a "generic" approach to algorithms for different data types. LAPACK implements algorithms for four different data types: - Single-precision real (S) - Double-precision real (D) - Single-precision complex (C) - Double-precision complex (Z) LAPACK does not rely on Fortran generic procedures or parameterized derived types, the closest analogs in Fortran to C++ templates. However, most of LAPACK's routines are implemented in such a way that one could generate all four versions automatically from a single "template."[4] As a result, we find LAPACK a useful analog to a C++ library of generic numerical algorithms, written using templates and traits classes. Numerical algorithm developers who are not C++ programmers have plenty of experience writing generic numerical algorithms. See, for example, "Templates for the Solution of Linear Systems,"[5] where "templates" means "recipes," not C++ templates. Thus, it should not be surprising to find design patterns in common between generic numerical algorithms not specifically using C++, and generic C++ libraries. In fact, our motivating example for keeping but renaming `numeric_limits::min' for floating-point types `T', will come from LAPACK's "floating-point traits" routine `_LAMCH'. LAPACK's "generic" approach means that algorithm developers need a way to access floating-point arithmetic properties as a function of data type, just as if a C++ developer were writing an algorithm templated on a floating-point type. Many linear algebra algorithms depend on those properties to avoid unwarranted overflow or underflow, and to get accurate results. As a result, LAPACK provides the `SLAMCH' and `DLAMCH' routines, that return machine parameters for the given real floating-point type (single-precision real resp. double-precision real). (One can derive from these the properties for corresponding complex numbers.) LAPACK routines have a uniform naming convention, where the first letter indicates the data type. LAPACK developers refer to the "generic" algorithm by omitting the first letter. For example, `_GEQRF' represents the same QR factorization for all data types for which it is implemented, in this case, `SGEQRF', `DGEQRF', `CGEQRF', and `ZGEQRF'. Hence, we refer to the "floating-point traits" routines `SLAMCH' and `DLAMCH' generically as `_LAMCH'. `_LAMCH' was designed to work on computers that may have non-IEEE-754 floating-point arithmetic. Older versions of the routine would actually compute the machine parameters. This is what LAPACK 3.1.1 does.[6] More recent versions of LAPACK, including the most recent version, 3.8.0, rely on Fortran 90 intrinsics to get the values of most of the machine parameters.[7] `_LAMCH' thus offers functionality analogous to `numeric_traits', for different real floating-point types. LAPACK's authors chose this functionality specifically for the needs of linear algebra algorithm development. `_LAMCH' gives developers several constants. The one most corresponding to the value of `numeric_traits::min()' is the "safe minimum" `sfmin', which is the smallest (or nearly the smallest) value `sfmin' such that `T (1.0) / sfmin' does not overflow. This is useful for algorithms (e.g., equilibration and balancing) that scale the rows and/or columns of a matrix to improve accuracy of a subsequent factorization. In the process of improving accuracy, one would not want to divide by too small of a number, and thus cause unwarranted underflow. In the most recent version of LAPACK, 3.8.0, LAPACK uses Fortran 90 intrinsic functions to compute `sfmin' as follows: ,---- | sfmin = tiny(zero) | small = one / huge(zero) | IF( small.GE.sfmin ) THEN | * | * Use SMALL plus a bit, to avoid the possibility of rounding | * causing overflow when computing 1/sfmin. | * | sfmin = small*( one+eps ) | END IF | rmach = sfmin `---- Here is the C++ equivalent: ,---- | template | T safe_minimum (const T& /* ignored */) { | constexpr T one (1.0); | constexpr T eps = std::numeric_limits::epsilon(); | constexpr T tiny = std::numeric_limits::min(); | constexpr T huge = std::numeric_limits::max(); | constexpr T small = one / huge; | T sfmin = tiny; | if(small >= tiny) { | sfmin = small * (one + eps); | } | return sfmin; | } `---- For IEEE 754 `float' and `double', the `IF' branch never gets taken. (LAPACK was originally written to work on computers that did not implement IEEE 754 arithmetic, so the extra branches may have made sense for earlier computer architectures. They are also useful as a conservative check of floating-point properties.) Thus, for `T=float' and `T=double', `sfmin' always equals `numeric_limits::min()'. This example shows that algorithm developers do actually want the value returned by `numeric_limits::min()' for real floating-point types `T'. However, we would prefer to give it a name that reflects its actual use, namely as a lower bound on the absolute value of a scaling factor. We suggest `safe_minimum_v' as the new name of this trait. We would accept other names, like `tiny_v' (from the Fortran intrinsic `TINY'). 4 Conclusion ============ We (the authors) have experience as developers and users of a C++ library of generic numerical algorithms, namely Trilinos.[8] Many other such libraries exist, including Eigen[9]. We also use the LAPACK library extensively, and have some experience modifying LAPACK algorithms.[10] We use and write traits classes that sometimes make use of `numeric_limits'. While we have found `numeric_limits' useful, we think it could benefit from the following changes: 1. Split out different traits into separate traits classes. 2. Don't let those classes compile for types for which they do not make sense. 3. For floating-point types `T', rename the equivalent of `numeric_limits::min' to something more indicative of its purpose in generic algorithms, e.g., `safe_minimum_v' or `tiny_v'. Our thanks to Walter Brown for P0437R1, and for helpful discussion and advice. 5 Funding and disclaimer ======================== Sandia National Laboratories is a multi-mission laboratory managed and operated by National Technology and Engineering Solutions of Sandia, LLC., a wholly owned subsidiary of Honeywell International, Inc., for the U.S. Department of Energy's National Nuclear Security Administration under contract DE-NA0003525. This paper describes objective technical results and analysis. Any subjective views or opinions that might be expressed in the paper do not necessarily represent the views of the U.S. Department of Energy or the United States Government. Footnotes _________ [1] L. Fousse, G. Hanrot, V. Lefèvre, P. Pélissier, and P. Zimmermann, "MPFR: A multiple-precision binary floating-point library with correct rounding," ACM Transactions on Mathematical Software, Vol. 33, No. 2, June 2007. [2] D. H. Bailey, Y. Hida, X. S. Li, and B. Thompson, "ARPREC: An Arbitrary Precision Computation Package," Lawrence Berkeley National Laboratory Technical Report LBNL-53651, 2002. [3] E. Anderson, Z. Bai, C. Bischof, S. Blackford, J. Demmel, J. Dongarra, J. Du Croz, A. Greenbaum, S. Hammarling, A. McKenney, and D. Sorensen, "LAPACK Users' Guide," 3rd ed., Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, 1999. [4] J.J. Dongarra, oral history interview by T. Haigh, 26 Apr. 2004, Society for Industrial and Applied Mathematics, Philadelphia, PA, USA; available from [http://history.siam.org/oralhistories/dongarra.htm]. [5] See, for example, R. Barrett, M. Berry, T. F. Chan, J. Demmel, J. Donato, J. Dongarra, V. Eijkhout, R. Pozo, C. Romine, and H. Van der Vorst, "Templates for the Solution of Linear Systems: Building Blocks for Iterative Methods," 2nd Edition, Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, 1994. "Templates" in the title does not mean C++ templates; it means something more like "design patterns." [6] For example, here is the implementation of `DLAMCH' in LAPACK 3.1.1: [http://www.netlib.org/lapack/explore-3.1.1-html/dlamch.f.html] [7] See, for example, the much shorter implementation of `DLAMCH' in LAPACK 3.8.0: [http://www.netlib.org/lapack/explore-html/d5/dd4/dlamch_8f_a06d6aa332f6f66e062e9b96a41f40800.html#a06d6aa332f6f66e062e9b96a41f40800] [8] M. A. Heroux et al., "An overview of the Trilinos project," ACM Transactions on Mathematical Software, Vol. 31, No. 3, Sep. 2005, pp. 397-423; M. A. Heroux and J. M. Willenbring, "A New Overview of The Trilinos Project," Scientific Programming, Vol 20, No. 2, 2012, pp. 83-88. See also: [Trilinos' GitHub site] (https://github.com/trilinos/Trilinos), and E. Bavier, M. Hoemmen, S. Rajamanickam, and Heidi Thornquist, "Amesos2 and Belos: Direct and Iterative Solvers for Large Sparse Linear Systems," Scientific Programming, Vol. 20, No. 3, 2012, pp. 241-255. [9] Gaël Guennebaud, Benoît Jacob, et al., "Eigen v3," [http://eigen.tuxfamily.org], 2010 [last accessed Nov. 2018]. See also Eigen's documentation for "Using custom scalar types": [http://eigen.tuxfamily.org/dox/TopicCustomizing_CustomScalar.html]. [10] See e.g., J. W. Demmel, M. Hoemmen, Y. Hida, and E. J. Riedy, "Nonnegative Diagonals and High Performance on Low-Profile Matrices from Householder QR," SIAM J. Sci. Comput., Vol. 31, No. 4, 2009, pp. 2832-2841. The authors later found out via a Matlab bug report that these changes to LAPACK's Householder reflector computation had subtle rounding error issues that broke one of LAPACK's dense eigensolver routines, so we ended up backing them out.