Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Obtaining the Size of a Unit In the Last Place - ULP
PrevUpHomeNext

Function ulp gives the size of a unit-in-the-last-place for a specified floating-point value.

Synopsis
#include <boost/math/special_functions/ulp.hpp>
namespace boost{ namespace math{

template <class FPT>
FPT ulp(const FPT& x);

template <class FPT, class Policy>
FPT ulp(const FPT& x, const Policy&);

}} // namespaces
Description - ulp

Returns one unit in the last place of x.

Corner cases are handled as follows:

  • If the argument is a NaN, then raises a domain_error.
  • If the argument is an infinity, then raises an overflow_error.
  • If the argument is zero then returns the smallest representable value: for example for type double this would be either std::numeric_limits<double>::min() or std::numeric_limits<double>::denorm_min() depending whether denormals are supported (which have the values 2.2250738585072014e-308 and 4.9406564584124654e-324 respectively).
  • If the result is too small to represent, then returns the smallest representable value.
  • Always returns a positive value such that ulp(x) == ulp(-x).

Important: The behavior of this function is aligned to that of Java's ulp function, please note however that this function should only ever be used for rough and ready calculations as there are enough corner cases to trap even careful programmers. In particular:

  • The function is asymmetrical, which is to say, given u = ulp(x) if x > 0 then x + u is the next floating-point value, but x - u is not necessarily the previous value. Similarly, if x < 0 then x - u is the previous floating-point value, but x + u is not necessarily the next value. The corner cases occur at power of 2 boundaries.
  • When the argument becomes very small, it may be that there is no floating-point value that represents one ULP. Whether this is the case or not depends not only on whether the hardware may sometimes support denormals (as signalled by std::numeric_limits<FPT>::has_denorm), but also whether these are currently enabled at runtime (for example on SSE hardware, the DAZ or FTZ flags will disable denormal support). In this situation, the ulp function may return a value that is many orders of magnitude too large.

In light of the issues above, we recommend that:

  • To move between adjacent floating-point values always use float_next, float_prior or nextafter (std::nextafter is another candidate, but our experience is that this also often breaks depending which optimizations and hardware flags are in effect).
  • To move several floating-point values away use float_advance.
  • To calculate the edit distance between two floats use Boost.Math float_distance.

There is none the less, one important use case for this function:

If it is known that the true result of some function is xt and the calculated result is xc, then the error measured in ulp is simply fabs(xt - xc) / ulp(xt).


PrevUpHomeNext