...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Loopback or round-tripping refers
to writing out a value as a decimal digit string using std::iostream
,
usually to a std::stringstream
, and then reading the string
back in to another value, and confirming that the two values are identical.
A trivial example using float
is:
float write; // Value to round-trip. std::stringstream ss; // Read and write std::stringstream. ss.precision(std::numeric_limits<T>::max_digits10); // Ensure all potentially significant bits are output. ss.flags(std::ios_base::fmtflags(std::ios_base::scientific)); // Use scientific format. ss << write; // Output to string. float read; // Expected. ss >> read; // Read decimal digits string from stringstream. BOOST_CHECK_EQUAL(write, read); // Should be the same.
and this can be run in a loop for all possible values of a 32-bit float.
For other floating-point types T
,
including built-in double
, it
takes far too long to test all values, so a reasonable test strategy is to
use a large number of random values.
T write; std::stringstream ss; ss.precision(std::numeric_limits<T>::max_digits10); // Ensure all potentially significant bits are output. ss.flags(f); // Changed from default iostream format flags if desired. ss << write; // Output to stringstream. T read; ss >> read; // Get read using operator>> from stringstream. BOOST_CHECK_EQUAL(read, write); read = static_cast<T>(ss.str()); // Get read by converting from decimal digits string representation of write. BOOST_CHECK_EQUAL(read, write); read = static_cast<T>(write.str(0, f)); // Get read using format specified when written. BOOST_CHECK_EQUAL(read, write);
The test at test_cpp_bin_float_io.cpp allows any floating-point type to be round_tripped using a wide range of fairly random values. It also includes tests compared a collection of stringdata test cases in a file.
One can make some comparisons with the output of
<number<cpp_bin_float<53, digit_count_2> >
which has the same number of significant bits (53) as 64-bit double precision floating-point.
However, although most outputs are identical, there are differences on some platforms caused by the implementation-dependent behaviours allowed by the C99 specification C99 ISO/IEC 9899:TC2, incorporated by C++.
"For e, E, f, F, g, and G conversions, if the number of significant decimal digits is at most DECIMAL_DIG, then the result should be correctly rounded. If the number of significant decimal digits is more than DECIMAL_DIG but the source value is exactly representable with DECIMAL_DIG digits, then the result should be an exact representation with trailing zeros. Otherwise, the source value is bounded by two adjacent decimal strings L < U, both having DECIMAL_DIG significant digits; the value of the resultant decimal string D should satisfy L<= D <= U, with the extra stipulation that the error should have a correct sign for the current rounding direction."
So not only is correct rounding for the full number of digits not required, but even if the optional recommended practice is followed, then the value of these last few digits is unspecified as long as the value is within certain bounds.
Note | |
---|---|
Do not expect the output from different platforms to be identical,
but |
C99 Standard for format specifiers, 7.19.6 Formatted input/output functions requires:
"The exponent always contains at least two digits, and only as many more digits as necessary to represent the exponent."
So to conform to the C99 standard (incorporated by C++)
#define BOOST_MP_MIN_EXPONENT_DIGITS 2
Confusingly, Microsoft (and MinGW) do not conform to this standard and provide
at least three digits, for example 1e+001
. So if you want the output to match
that from built-in floating-point types on compilers that use Microsofts
runtime then use:
#define BOOST_MP_MIN_EXPONENT_DIGITS 3
Also useful to get the minimum exponent field width is
#define BOOST_MP_MIN_EXPONENT_DIGITS 1
producing a compact output like 2e+4
,
useful when conserving space is important.
Larger values are also supported, for example, value 4 for 2e+0004
which may be useful to ensure that
columns line up.