'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 fordouble, but certainly does not work forlong double(std::numeric_limits<long double>::min()is printed as "0.", followed by 999 zeros). - It is extremely wasteful, because e.g. for
floatit 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::fixedmakes 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
setprecisionis 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 originalfloatvalue, 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 |
