I want to match the output of boost::spirit::karma::real_generator
to the output of std::stringstream
.
The following code converts the double 8612.0078125
to std::string
with precision 6 using:
stringstream
and std::setprecision
boost::spirit::karma::real_generator
However, they behave differently in the last digit. The output:
8612.007812
8612.007813
I checked karma::real_policies
if there was a policy for rounding behavior, but n
is already 7813 when fraction_part
is called.
#include <boost/spirit/include/karma.hpp>
#include <iomanip>
template <typename T>
struct precision_policy : boost::spirit::karma::real_policies<T>
{
int floatfield(T n) const { return boost::spirit::karma::real_policies<T>::fmtflags::fixed; } // Always Fixed
bool trailing_zeros(T n) const{ return true; }
precision_policy(int prec):precision_(prec){}
int precision(T n) const { return precision_; }
int precision_;
};
// https://stackoverflow.com/questions/42255919/using-boost-karma-to-replace-stdstringstream-for-double-to-stdstring-convers
std::string ToStringFixedKarma(double d, const unsigned int width = 6)
{
using boost::spirit::karma::real_generator;
using boost::spirit::ascii::space;
using boost::spirit::karma::generate;
real_generator<double,precision_policy<double> > my_double_(width);
std::string s;
std::back_insert_iterator<std::string> sink(s);
generate(sink, my_double_, d);
return s;
}
int main(){
double my_double = 8612.0078125;
std::stringstream description;
description << std::fixed << std::setprecision(6)
<< my_double << "\n";
std::cout << description.str();
std::cout << ToStringFixedKarma(my_double) << "\n";
}
I hate to be that guy, but this strongly feels like a problem of square pegs and round holes.
Karma has its use, but in isolation to format reals, I would not consider it inevitable.
The linked answer suggests that you might be after the performance boost. I suspect that this can also be had by using a stream on the same std::string
instance repeatedly. Here is a simple take that - obviously - replicates the iostream
behaviour:
std::string ToStringFixedNoKarma(double d, const unsigned int width = 6) {
using D = boost::iostreams::back_insert_device<std::string>;
std::string s;
boost::iostreams::stream<D> ss(D{s});
ss << std::fixed << std::setprecision(width) << d;
return s;
}
You can expect it to be faster due to the ability to move from the string result and/or to reuse the backing string instance.
Other ideas/source of inspiration:
Boost Convert has an array of conversion methods, including benchmarks
Also note the remark about Karma performance.
If you're after correctness and speed, consider Libfmt:
- Fast IEEE 754 floating-point formatter with correct rounding, shortness and round-trip guarantees
They too publish benchmarks:
C++17 has the new to_chars
in <charconv>
. Sadly GCC doesn't implement it fully yet (but MSVC does).
In an attempt to be helpful a bit here's a side-by-side of many of the approaches mentioned:
#include <boost/convert.hpp>
#include <boost/convert/lexical_cast.hpp>
#include <boost/convert/parameters.hpp>
#include <boost/convert/printf.hpp>
#include <boost/convert/stream.hpp>
#include <boost/convert/strtol.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <fmt/printf.h>
#include <charconv>
#include <iostream>
using Reference = boost::multiprecision::cpp_dec_float_100;
std::string ToStringFixedKarma(double d, const unsigned int width = 6) {
using D = boost::iostreams::back_insert_device<std::string>;
std::string s;
boost::iostreams::stream<D> ss(D{s});
ss << std::fixed << std::setprecision(width) << d;
return s;
}
void comparisons(std::string_view label, Reference value, auto converter) {
double d = value.convert_to<double>();
float f = value.convert_to<float>();
std::cout << " ---- " << label << "\n";
std::cout << "float" << converter(f) << "\n";
std::cout << "double" << converter(d) << "\n";
}
int main() {
namespace cnv = boost::cnv;
namespace arg = boost::cnv::parameter;
cnv::cstream _cs;
cnv::lexical_cast _lc; // not able to control format
cnv::strtol _stl;
cnv::printf _pf;
Reference const reference("8612.0078125");
std::cout << "Reference: " << reference.str(20, std::ios::fixed) << "\n";
_cs(std::fixed)(std::setprecision(6));
_stl(arg::notation = cnv::notation::fixed)(arg::precision = 6);
_pf(arg::notation = cnv::notation::fixed)(arg::precision = 6);
auto cs = cnv::apply<std::string>(boost::cref(_cs));
auto stl = cnv::apply<std::string>(boost::cref(_stl));
auto pf = cnv::apply<std::string>(boost::cref(_pf));
auto lc = cnv::apply<std::string>(boost::cref(_lc));
comparisons("cstream", reference, cs);
comparisons("strtol", reference, stl);
comparisons("printf", reference, pf);
comparisons("libfmt", reference, [](auto v) { return fmt::format(FMT_STRING("{:.10}"), v); });
#ifdef __cpp_lib_to_chars
comparisons("charconv", reference,
[buf = std::array<char, 30>{}](auto v) mutable {
auto r = std::to_chars(buf.data(), buf.data() + buf.size(),
v, std::chars_format::fixed, 6);
return std::string_view(buf.data(), r.ptr - buf.begin());
});
#endif
comparisons("lexical_cast", reference, lc);
}
Prints
Reference: 8612.00781250000000000000
---- cstream
float8612.007812
double8612.007812
---- strtol
float8612.007813
double8612.007813
---- printf
float8612.007812
double8612.007812
---- libfmt
float8612.007812
double8612.007812
---- lexical_cast
float8612.00781
double8612.0078125