将 std::stringstream 的回合行为与 boost::spirit::karma::real_generator 匹配
Match round behavior of std::stringstream with boost::spirit::karma::real_generator
我想将 boost::spirit::karma::real_generator
的输出与 std::stringstream
的输出相匹配。
以下代码使用以下代码将双精度 8612.0078125
转换为精度为 6 的 std::string
:
stringstream
和 std::setprecision
boost::spirit::karma::real_generator
但是,它们在最后一位的表现不同。
输出:
8612.007812
8612.007813
我检查了 karma::real_policies
是否有舍入行为的策略,但是当调用 fraction_part
时 n
已经是 7813。
#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_;
};
//
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";
}
我讨厌成为那个人,但这强烈地感觉像是一个方钉和圆孔的问题。
Karma 有它的用处,但单独格式化实数,我不认为它是不可避免的。
链接的答案表明您可能正在寻求性能提升。我怀疑这也可以通过在同一个 std::string
实例上重复使用流来实现。这是一个简单的做法 - 显然 - 复制了 iostream
行为:
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;
}
由于能够从字符串结果 and/or 移动到重用支持字符串实例,因此您可以预期它会更快。
其他ideas/source灵感:
Boost Convert 有一系列的转换方法,包括benchmarks
另请注意有关 Karma 性能的备注。
如果您追求正确性和速度,请考虑 Libfmt:
- Fast IEEE 754 floating-point formatter with correct rounding,
shortness and round-trip guarantees
他们也发布了基准:
C++17 有新的 to_chars
in <charconv>
. Sadly GCC doesn't implement it fully(但 MSVC 有)。
为了提供一点帮助,这里列出了所提到的许多方法:
#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);
}
版画
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
我想将 boost::spirit::karma::real_generator
的输出与 std::stringstream
的输出相匹配。
以下代码使用以下代码将双精度 8612.0078125
转换为精度为 6 的 std::string
:
stringstream
和std::setprecision
boost::spirit::karma::real_generator
但是,它们在最后一位的表现不同。 输出:
8612.007812
8612.007813
我检查了 karma::real_policies
是否有舍入行为的策略,但是当调用 fraction_part
时 n
已经是 7813。
#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_;
};
//
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";
}
我讨厌成为那个人,但这强烈地感觉像是一个方钉和圆孔的问题。
Karma 有它的用处,但单独格式化实数,我不认为它是不可避免的。
链接的答案表明您可能正在寻求性能提升。我怀疑这也可以通过在同一个 std::string
实例上重复使用流来实现。这是一个简单的做法 - 显然 - 复制了 iostream
行为:
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;
}
由于能够从字符串结果 and/or 移动到重用支持字符串实例,因此您可以预期它会更快。
其他ideas/source灵感:
Boost Convert 有一系列的转换方法,包括benchmarks
另请注意有关 Karma 性能的备注。
如果您追求正确性和速度,请考虑 Libfmt:
- Fast IEEE 754 floating-point formatter with correct rounding, shortness and round-trip guarantees
他们也发布了基准:
C++17 有新的
to_chars
in<charconv>
. Sadly GCC doesn't implement it fully(但 MSVC 有)。
为了提供一点帮助,这里列出了所提到的许多方法:
#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);
}
版画
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