'std::cout print all digits of float value

I have this function:

template<typename T> // T can be float, double or long double
void printAllDigits(T value)
{
    std::cout << std::fixed << std::setprecision(999) << value;
}

It's a dumb implementation to print all digits of a floating point value.

This has some problems:

  • I can't guarantee that it works for all float types. It seems to work for float (999 digits is probably enough), and maybe works for double, but certainly does not work for long double (std::numeric_limits<long double>::min() is printed as "0.", followed by 999 zeros).
  • It is extremely wasteful, because e.g. for float it always prints a ton of trailing zeros, even though those can never be non-zero.

But it does some things right:

  • I never want scientific notation, so std::fixed makes sense.
  • I have code that strips the trailing zeros (akin to what is sugessted in Remove trailing zero in C++), which works well with this approach.
  • I don't have to write down separate code paths or constants for the different float types.
  • If the argument to setprecision is large enough, this actually prints all digits without rounding.
  • I can copy the output, plonk it back into a code file (and make sure to add ".0f" and such where necessary) and get the same floating point value.
  • "Unnecessary" digits are not rounded away. printAllDigits(0.1f) prints "0.100000001490116119384765625000000...". Printing "0.1" would be sufficient to get back to the original float value, but I still need the function to print all of those digits.

How can I make that function less wasteful while maintaining my requirements?

std::numeric_limits<T>::max_digits10 is incorrect, since it is too small! std::numeric_limits<float>::min() gets printed as "0.000000000" instead of "0.0000000000000000000000000000000000000117549435082228750796873653722224567781866555677208752150875170627841725945472717285156250000000..."



Solution 1:[1]

999 digits is probably enough

Typical float needs about 150 digits to the right of the decimal point and double needs about 1075 to print the exact value in some cases. Setting precision that high may not produce the exact output and one will need specialized code to do so.

Forego the "I never want scientific notation" and use decimal floating point notation, printing with at least 6 (float), 9 (double) significant places is enough to round-trip values from floating-point to text to floating point.

Or use hexadecimal notation.

Solution 2:[2]

Here is another answer that will avoid removing the trailing zeroes.

The idea is to scale the value (multiply by radix^scale) until we obtain a significand int.frac with a null fraction part.

This works for radix == 2 or 10.

// return the number of fractional decimal digits required to print a floating point exact value
template<class FloatType> int required_precision( FloatType value )
{
    assert( std::numeric_limits<FloatType>::radix == 2 ||
            std::numeric_limits<FloatType>::radix == 10 );
    if (! std::isfinite(value) ) return 0;
    // exponent of 0.0 is implementation defined, so don't rely on ilogb
    if (value == FloatType(0) ) return 1;
    // use ilogb for initial guess of scale                                                                                                                                         int exponent = std::ilogb( value );
    int scale =  - exponent;
    FloatType significand = std::scalbn( std::abs(value) , scale );
    // scale up until fraction part is null
    while( significand-std::trunc(significand) != FloatType(0) ) {
        scale += 1;
        significand = std::scalbn( significand - std::trunc(significand) , 1 );
    }
    // print at least 1 fractional zero for case of integral value
    return std::max(1,scale);
}

This should work whatever the radix.

If the radix is 2, and if the number of digits does not exceed that of unsigned long long, then we can scale only once so as to obtain an integer significand, and count trailing zeroes of that significand to adjust the scale.

template<class FloatType> int required_precision( FloatType value )
{
    assert( std::numeric_limits<FloatType>::radix == 2 &&
            std::numeric_limits<FloatType>::digits <= std::numeric_limits<unsigned long long>::digits );
    if (! std::isfinite(value) ) return 0;
    // exponent of 0.0 is implementation defined, so don't rely on ilogb
    if (value == FloatType(0) ) return 1;
    int exponent = std::ilogb( value );
    // quick check if exponent is greater than max number of digits, then it is an integral value
    if( exponent >= std::numeric_limits<FloatType>::digits ) return 1;
    // scale significand to integer
    int scale =  std::numeric_limits<FloatType>::digits - 1 - exponent;
    FloatType significand = std::scalbn( std::abs(value) , scale );
    unsigned long long int_significand = static_cast<unsigned long long>(significand);
    // replace this by your own trick to count trailing zeroes if not compiling with gcc/g++
    return std::max(1,scale - __builtin_ctzll( int_significand ));
}

if unsigned long long has not enough bits to hold the significand of some FloatType, then the scaling has to be split with a solution in between the 2 above:

template<class FloatType> int required_precision( FloatType value )
{
    assert( std::numeric_limits<FloatType>::radix == 2 );
    if (! std::isfinite(value) ) return 0;
    if (value == FloatType(0) ) return 1;
    int exponent = std::ilogb( value );
    if( exponent >= std::numeric_limits<FloatType>::digits ) return 1;
    // scale significand to integer : care to not overflow UNSIGNED_LONG_LONG_MAX
    int max_bits = std::numeric_limits<unsigned long long>::digits;
    int scale =  max_bits - 1 - exponent;
    FloatType significand = std::scalbn( std::abs(value) , scale );
    while( significand-std::trunc(significand) != FloatType(0) ) {
        scale += max_bits;
        significand = std::scalbn( significand - std::trunc(significand) , max_bits );
    }
    unsigned long long int_significand = static_cast<unsigned long long>(significand);
    return std::max(1,scale - __builtin_ctzll( int_significand ));
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2