Letter to the Editor

Letter to the Editor

By Lion Gutjahr, Thaddaeus Frogley

Overload, 23(125):22-24, February 2015


I recently received an email regarding an article from an Overload published way back in 2001. It is wonderful to hear people are still looking back through items people have written, no matter how long ago. The email drew my attention to C++11’s std::numeric_limits<T>::lowest() , which I hadn’t come across so I thought others might be in the same situation. Lion has kindly taken the time to formalise his initial observations into the following letter, and I have included a response from the original author, Thaddaeus Frogley.

Dear Editor,

I have just read the Overload article ‘An introduction to C++ Traits’ [ Frogley01 ] and noticed that the initialisation of largest in the first code fragment (the one adapted from [Veldhuizen]):

  T largest = std::numeric_limits< T >::min();

is done using std::numeric_limits<T>::min() where it could now be C++11’s std::numeric_limits<T>::lowest() for cases where the type T is float or double and all array members are below or equal to zero.

Although I couldn’t find an explicit reference, this seems to originate from C where INT_MIN and FLT_MIN or DBL_MIN show the same semantic difference; some people guess that it is because the libraries for rational and for integral numbers have been developed by different groups. Fernando Cacciola explains it very nicely in the N1880 proposal to the C++ standard [ N1880 ]:

numeric_limits::min() (18.2.1.2) is defined with a meaning which is inconsistent across integer and floating-point types. Specifically, for integer types, it is the minimum finite value whereas for floating point types it is the minimum positive normalized value. The inconsistency here lies in the interpretation of minimum: in the case of integer types, it signifies lowest, while for floating point types, it signifies smallest non-zero.

N2348 adds some history [ N2348 ]:

At Mont Tremblant, Pete Becker noted that the wording was flawed, because not all floating point representations have the feature that the most negative representable finite value is the negative of the most positive representable finite value.

An example (Listing 1) shows the different behaviour.

#include <limits>
#include <iostream>

template< class T >
T findMax(const T * data, const size_t numItems) {
  // Obtain the minimum value for type T 
  T largest = std::numeric_limits< T >::min();
  for (unsigned int i = 0; i<numItems; ++i)
    if (data[i] > largest)
      largest = data[i];
  return largest;
}

template< class T >
T findMaxNew(const T * data, const size_t numItems) {
  // Obtain the minimum value for type T 
  T largest = std::numeric_limits< T >::lowest();
  for (unsigned int i = 0; i<numItems; ++i)
    if (data[i] > largest)
      largest = data[i];
  return largest;
}
template< class T >
void printAry(const T * data, const size_t numItems) {
  for (unsigned int i = 0; i<numItems; ++i)
    std::cout << data[i] << " ";
  std::cout << std::endl;
}
void test(float* ary, size_t nof) {
  printAry(ary, nof);
  float maxFloat = findMax<float>(ary, nof);
  float newMaxFloat = 
    findMaxNew<float>(ary, nof);
  std::cout << "max in only negative floats is "
            << maxFloat;
  if (std::abs(maxFloat - newMaxFloat) >
    std::numeric_limits<float>::epsilon())
      std::cout << " but should have been " 
                << newMaxFloat;
  std::cout << std::endl << std::endl;
}

int main() {
  float onlyNegativeFloats[] = { -2, -1, -3 };
  std::cout << "array of only negative floats: ";
  test(onlyNegativeFloats, 3);
  float positiveAndNegativeFloats[] = {
    2, -1, -3 };
  std::cout << "array of positive and negative
    floats: ";
  test(positiveAndNegativeFloats, 3);
  return 0;
}
			
Listing 1

Because std::lowest() is not available in C++98, an alternative might be needed. I created a second example which uses the traits technique (see Listing 2).

#include <cstdlib>
#include <cmath>
#include <limits>
#include <iostream>
#include <string>

template< class T >
T findMax(const T * data, const size_t numItems)
{
  // Obtain the minimum value for type T 
  T largest =
    std::numeric_limits< T >::min();
  for (unsigned int i = 0; i<numItems; ++i)
    if (data[i] > largest)
      largest = data[i];
  return largest;
}

namespace detail {
  template< class T, bool isFloat >
  struct cpp98_numeric_limits_lowest {};
  template< class T >
  struct cpp98_numeric_limits_lowest<T, true> {
    static T value() {
      return -std::numeric_limits<T>::max(); }
  };
  template< class T >
  struct cpp98_numeric_limits_lowest<T, false> {
    static T value() {
      return std::numeric_limits<T>::min(); }
  };
} // end namespace detail

template< class T >
T cpp98_numeric_limits_lowest() {
  return detail::cpp98_numeric_limits_lowest< T,
    std::numeric_limits<T>::is_specialized &&
    !std::numeric_limits<T>::is_integer>
        ::value();
}

template< class T >
T findMaxNew(const T * data,
  const size_t numItems) {
  // Obtain the minimum value for type T 
  T largest = cpp98_numeric_limits_lowest<T>();
  for (unsigned int i = 0; i<numItems; ++i)
    if (data[i] > largest)
      largest = data[i];
  return largest;
}

template< class T >
void printAry(const T * data,
  const size_t numItems) {
  for (unsigned int i = 0; i<numItems; ++i)
    std::cout << data[i] << " ";
  std::cout << std::endl;
}

namespace detail {
  template< typename T, bool isFloat >
  struct areEqual {};
  template< typename T >
  struct areEqual<T, true > {
    static bool value(const T& a, const T& b) {
      return std::abs(a - b) <=
      std::numeric_limits<T>::epsilon(); }
  };
  
    template< class T >
  struct areEqual<T, false > {
    static bool value(const T& a, const T& b) {
      return a == b; }
  };
} // end namespace detail

template< class T >
T areEqual(const T& a, const T& b) {
  return detail::areEqual< T,
    std::numeric_limits<T>::is_specialized &&
    !std::numeric_limits<T>::is_integer>
        ::value(a, b);
}

template< class T >
void test(const std::string& desc,
   const T * data, const size_t numItems) {
  std::cout << "array of " << desc << ": ";
  printAry(data, numItems);

  T maxval = findMax<T>(data, numItems);
  T newMaxval = findMaxNew<T>(data, numItems);

  std::cout << "max in " << desc << " is "
    << maxval;
  if (!areEqual<T>(maxval, newMaxval))
    std::cout << " but should have been " 
    << newMaxval;
  std::cout << std::endl << std::endl;
}
int main()
{
  {float vals[] = { -2, -1, -3 };
  test<float>("only negative floats", vals, 3); }

  {float vals[] = { 2, -1, -3 };
  test<float>("positive and negative floats",
    vals, 3); }

  {double vals[] = { -2, -1, -3 };
  test<double>("only negative doubles",
    vals, 3); }

  {double vals[] = { 2, -1, -3 };
  test<double>("positive and negative doubles",
    vals, 3); }

  {long double vals[] = { -2, -1, -3 };
  test<long double>("only negative long doubles",
    vals, 3); }

  {long double vals[] = { 2, -1, -3 };
  test<long double>("positive and negative long
    doubles", vals, 3); }

  {int vals[] = { -2, -1, -3 };
  test<int>("only negative ints", vals, 3); }

  {int vals[] = { 2, -1, -3 };
  test<int>("positive and negative ints", vals,
    3); }

  {unsigned short vals[] = { 2, 3, 1 };
  test<unsigned short>("unsigned shorts", vals,
    3); }

  return 0;
}
			
Listing 2

Please note that it could be possible to distinguish at compile time between pre and post C++11 so that newer compilers could be routed to lowest() directly, but I couldn’t find a short and clean way to do that, although #if (__cplusplus > 199711L) would come pretty close for many compilers.

Regards,

Lion Gutjahr

References

[Frogley01] Frogley, Thaddaeus ‘An introduction to C++ Traits’, Overload Journal #43 (June 2001)

[N1880] N1880 proposal to the C++ standard http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1880.htm

[N2348] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2348.pdf

Response from Thaddaeus

If anything, it highlights the need to introduce lowest, if there is (was) no clear and straightforward way to achieve the same goal in the C++ of 2001, noting that -max doesn’t work for unsigned integers!

My preferred solution these days would to write something like this (and this is also a more language agnostic approach):

  template< class T >
  T findMax(const T* data, int numItems)
  {
      assert(numItems>0);
      // Obtain the minimum value for type T
      int i=0;
      T largest = data[i++];
      for (;i < numItems; ++i)
          if (data[i] > largest)
              largest = data[i];
      return largest;
  }

which avoids the problem altogether, provided numItems != 0 .






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.