该机制存在,但隐藏得更深,因为解析浮点数的细节比整数多得多。
qi::double_(和float_)实际上是qi::real_parser<double, qi::real_policies<double> > 的实例。
policies 是关键。它们管理所接受格式的所有细节。
这里是RealPolicies Expression Requirements
| Expression |
Semantics |
RP::allow_leading_dot |
Allow leading dot. |
RP::allow_trailing_dot |
Allow trailing dot. |
RP::expect_dot |
Require a dot. |
RP::parse_sign(f, l) |
Parse the prefix sign (e.g. '-'). Return true if successful, otherwise false. |
RP::parse_n(f, l, n) |
Parse the integer at the left of the decimal point. Return true if successful, otherwise false. If successful, place the result into n. |
RP::parse_dot(f, l) |
Parse the decimal point. Return true if successful, otherwise false. |
RP::parse_frac_n(f, l, n, d) |
Parse the fraction after the decimal point. Return true if successful, otherwise false. If successful, place the result into n and the number of digits into d |
RP::parse_exp(f, l) |
Parse the exponent prefix (e.g. 'e'). Return true if successful, otherwise false. |
RP::parse_exp_n(f, l, n) |
Parse the actual exponent. Return true if successful, otherwise false. If successful, place the result into n. |
RP::parse_nan(f, l, n) |
Parse a NaN. Return true if successful, otherwise false. If successful, place the result into n. |
RP::parse_inf(f, l, n) |
Parse an Inf. Return true if successful, otherwise false. If successful, place the result into n. |
让我们实施您的政策:
namespace policies {
/* mandatory sign (or space) fixed widths, 'D+' or 'D-' exponent leader */
template <typename T, int IDigits, int FDigits, int EDigits = 2>
struct fixed_widths_D : qi::strict_ureal_policies<T> {
template <typename It> static bool parse_sign(It& f, It const& l);
template <typename It, typename Attr>
static bool parse_n(It& f, It const& l, Attr& a);
template <typename It> static bool parse_exp(It& f, It const& l);
template <typename It>
static bool parse_exp_n(It& f, It const& l, int& a);
template <typename It, typename Attr>
static bool parse_frac_n(It& f, It const& l, Attr& a, int& n);
};
} // namespace policies
注意:
- 我保持属性类型通用。
- 我也基于严格的实现
strict_urealpolicies 以减少工作量。基类没有
支持符号,并且需要一个强制性的小数分隔符 ('.'),这使得它“严格”并且只拒绝整数
- 您的问题格式要求整数部分为 1 位,整数部分为 12 位
指数的分数和 2,但我没有硬编码,所以我们可以重用
其他固定宽度格式的政策(
IDigits、FDigits、EDigits)
让我们一个一个地检查我们的覆盖:
bool parse_sign(f, l)
格式是定宽的,所以想接受
这样标志总是需要一个输入字符:
template <typename It> static bool parse_sign(It& f, It const&l)
{
if (f != l) {
switch (*f) {
case '+':
case ' ': ++f; break;
case '-': ++f; return true;
}
}
return false;
}
bool parse_n(f, l, Attr& a)
最简单的部分:我们只允许在分隔符之前有一位数 (IDigits) 无符号整数部分。幸运的是,整数解析是比较常见和简单的:
template <typename It, typename Attr>
static bool parse_n(It& f, It const& l, Attr& a)
{
return qi::extract_uint<Attr, 10, IDigits, IDigits, false, true>::call(f, l, a);
}
bool parse_exp(f, l)
也很简单:我们总是需要'D':
template <typename It> static bool parse_exp(It& f, It const& l)
{
if (f == l || *f != 'D')
return false;
++f;
return true;
}
bool parse_exp_n(f, l, int& a)
至于指数,我们希望它是固定宽度的,这意味着符号是
强制的。因此,在提取宽度为 2 的有符号整数 (EDigits) 之前,我们确保
标志存在:
template <typename It>
static bool parse_exp_n(It& f, It const& l, int& a)
{
if (f == l || !(*f == '+' || *f == '-'))
return false;
return qi::extract_int<int, 10, EDigits, EDigits>::call(f, l, a);
}
bool parse_frac_n(f, l, Attr&, int& a)
问题的实质,也是建立在现有解析器上的原因。
小数位可以被认为是整数,但是由于以下原因存在问题
前导零很重要以及总位数可能
超过我们选择的任何整数类型的容量。
所以我们做了一个“技巧”——我们解析一个无符号整数,但忽略任何多余的
不适合的精度:实际上我们只关心位数。我们
然后检查这个数字是否符合预期:FDigits。
然后,我们交给基类实现来实际计算
对于任何通用数字类型T(满足the
minimum
requirements),结果值正确。
template <typename It, typename Attr>
static bool parse_frac_n(It& f, It const& l, Attr& a, int& n)
{
It savef = f;
if (qi::extract_uint<Attr, 10, FDigits, FDigits, true, true>::call(f, l, a)) {
n = static_cast<int>(std::distance(savef, f));
return n == FDigits;
}
return false;
}
总结
您可以看到,站在现有的、经过测试的代码的肩膀上,我们已经完成并且可以很好地解析我们的数字:
template <typename T>
using X19_type = qi::real_parser<T, policies::fixed_widths_D<T, 1, 12, 2>>;
现在您的代码按预期运行:Live On Coliru
template <typename T>
using X19_type = qi::real_parser<T, policies::fixed_widths_D<T, 1, 12, 2>>;
int main() {
using It = std::string::const_iterator;
using namespace qi::labels;
qi::uint_parser<uint16_t, 10, 4, 4> i4;
X19_type<double> x19;
qi::rule<It, double()> X19 = x19 //
| qi::repeat(19)[' '] >> qi::attr(0.0);
for (std::string const str : {
"1234 1234",
"1234 0.000000000000D+001234",
"1234 7.065432100000D+001234",
"1234-7.006543210000D+001234",
"1234 0.065432100000D+031234",
"1234 0.065432100000D-301234",
}) {
It f = str.cbegin(), l = str.cend();
RECORD rec;
if (qi::parse(f, l, (i4 >> X19 >> i4), rec)) {
std::cout << "{a:" << rec.a << ", b:" << std::setprecision(12)
<< rec.b << ", c:" << rec.c << "}\n";
} else {
std::cout << "Parse fail (" << std::quoted(str) << ")\n";
}
}
}
打印
{a:1234, b:0, c:1234}
{a:1234, b:0, c:1234}
{a:1234, b:7.0654321, c:1234}
{a:1234, b:-7.00654321, c:1234}
{a:1234, b:65.4321, c:1234}
{a:1234, b:6.54321e-32, c:1234}
小数
现在,可以以超过
double 的精度。从
十进制数字到不精确的二进制表示。展示如何选择
对于通用的T 已经满足了这一点,让我们用十进制类型进行实例化
允许 64 位有效的十进制小数位:
Live On Coliru
using Decimal = boost::multiprecision::cpp_dec_float_100;
struct RECORD {
uint16_t a{};
Decimal b{};
uint16_t c{};
};
template <typename T>
using X71_type = qi::real_parser<T, policies::fixed_widths_D<T, 1, 64, 2>>;
int main() {
using It = std::string::const_iterator;
using namespace qi::labels;
qi::uint_parser<uint16_t, 10, 4, 4> i4;
X71_type<Decimal> x71;
qi::rule<It, Decimal()> X71 = x71 //
| qi::repeat(71)[' '] >> qi::attr(0.0);
for (std::string const str : {
"1234 6789",
"2345 0.0000000000000000000000000000000000000000000000000000000000000000D+006789",
"3456 7.0000000000000000000000000000000000000000000000000000000000654321D+006789",
"4567-7.0000000000000000000000000000000000000000000000000000000000654321D+006789",
"5678 0.0000000000000000000000000000000000000000000000000000000000654321D+036789",
"6789 0.0000000000000000000000000000000000000000000000000000000000654321D-306789",
}) {
It f = str.cbegin(), l = str.cend();
RECORD rec;
if (qi::parse(f, l, (i4 >> X71 >> i4), rec)) {
std::cout << "{a:" << rec.a << ", b:" << std::setprecision(65)
<< rec.b << ", c:" << rec.c << "}\n";
} else {
std::cout << "Parse fail (" << std::quoted(str) << ")\n";
}
}
}
打印
{a:2345, b:0, c:6789}
{a:3456, b:7.0000000000000000000000000000000000000000000000000000000000654321, c:6789}
{a:4567, b:-7.0000000000000000000000000000000000000000000000000000000000654321, c:6789}
{a:5678, b:6.54321e-56, c:6789}
{a:6789, b:6.54321e-89, c:6789}
比较如何使用二进制long double 表示将have lost
accuracy here:
{a:2345, b:0, c:6789}
{a:3456, b:7, c:6789}
{a:4567, b:-7, c:6789}
{a:5678, b:6.5432100000000000002913506043764438647482181234694313277925965188e-56, c:6789}
{a:6789, b:6.5432100000000000000601529073044049029207066886931600941449474131e-89, c:6789}
奖金:可选
在当前 RECORD 中,缺失的双打被默默地视为0.0。这可能不是最好的:
struct RECORD {
uint16_t a{};
optional<Decimal> b{};
uint16_t c{};
};
// ...
qi::rule<It, optional<Decimal>()> X71 = x71 //
| qi::repeat(71)[' '];
现在输出是Live On Coliru:
{a:1234, b:--, c:6789}
{a:2345, b: 0, c:6789}
{a:3456, b: 7.0000000000000000000000000000000000000000000000000000000000654321, c:6789}
{a:4567, b: -7.0000000000000000000000000000000000000000000000000000000000654321, c:6789}
{a:5678, b: 6.54321e-56, c:6789}
{a:6789, b: 6.54321e-89, c:6789}
总结/添加单元测试!
这很多,但可能不是您需要的全部。
请记住,您仍然需要适当的单元测试,例如X19_type。思考
在您可能遇到/想要接受/想要拒绝的所有边缘情况中:
- 我没有更改任何处理 Inf 或 NaN 的基本策略,所以你
可能想缩小这些差距
- 你可能真的想接受
" 3.141 ",
" .999999999999D+0 " 等等?
所有这些都是对策略的非常简单的更改,但是,如您所知,代码
没有测试就坏了。