【问题标题】:Convert a specifically formated number to long/double in C++在 C++ 中将特定格式的数字转换为 long/double
【发布时间】:2011-12-19 03:07:04
【问题描述】:

我正在为一种编程语言编写解释器,并且我正在使用 C++ 进行,坦率地说,我对这种语言很陌生。

我想要完成的是在std::string 中转换特定的浮点格式 到双(或其他)。我希望它完全独立于语言环境并尽可能健壮。

我有两种情况:

  • 整数:它们应该是从 0 到 9 的连续数字,带或不带前导减号(不允许加号,允许前导零)
  • 浮点数:[整数部分].[小数部分] 带或不带前导减号且不带任何千位分隔符。可以省略整个部分或小数部分(例如.44.),但不能同时省略两者

我希望它是“C++ 方式”。有没有我可以用来指定自定义数字格式的函数(有点像 PHP 中的date)。

我将非常感谢提供的任何指针或代码-sn-p。谢谢!

【问题讨论】:

  • 定义完全独立于语言环境。你假设C 语言环境呢?否则“1.234”是不明确的。在欧盟(某个地方,不知道具体在哪里),那是“一千二百三十四”。在C 区域设置(和美国)中,这是“千分之二百三十四”
  • 点作为小数分隔符,而不是千位。就像在美国地区一样,我只是希望它独立于客户端计算机设置。
  • @Mooing Duck:那是德国(至少)。
  • @bitmask: 或者我来自斯洛文尼亚,是的 :)
  • 事实上,绝大多数欧洲国家。我们真的应该让123,456€ euro-FP-literals 进入 C++2x ;)

标签: c++ floating-point integer


【解决方案1】:

假设您的意思是字符串位于 C 语言环境中:

template<class T>
std::string tostring(const T& input)
{
    stringstream ss;
    if (!(ss << input))
        throw std::runtime_error("cannot convert!");
    return ss.str();
}

template<class T>
void fromstring(const std::string& input, T& output)
{
    stringstream ss(input);
    if (!(ss >> output) || ss)
        throw std::runtime_error("cannot convert!");
}
//Passes output as parameter, in case it's not copiable.

int main() {

    float pi = 3.14159f;  //pi to string and back
    std::string strpi = tostring(pi);
    fromstring(strpi, pi);

    std::ifstream in("in.txt");  //copies a file through a string
    std::string file = tostring(in);
    std::ofstream out("out.txt");
    fromstring(file, out);

    return 0;
}

【讨论】:

  • 那么,我给出的约束正是 C语言环境?
  • C 区域设置(与数字 IO 相关)将正确解析您描述的格式的数字。语言环境指定排序、时间、数字和货币格式以及字符处理。
  • @Tibor:在某些情况下,它可能允许超出您的规范,如 sehe 所述。
  • 这是一个好方法,但如果 5.4 提供了 long 类型,它不会抛出异常。它只会读取 5 并保留小数部分。
  • 没关系,我通过检查流的eof() 来修复它。谢谢!
【解决方案2】:

我不知道 iostreams 是否支持严格的输入格式。

但是,您可以使用 Boost Spirit:

带有 RealPolicy 的标准真实解析器

http://www.boost.org/doc/libs/1_47_0/libs/spirit/doc/html/spirit/qi/reference/numeric/real.html

这将允许您明确定义指数、符号和任何(数千个)分隔符所接受的格式。这也是一种相当复杂的方法,但它非常快速且非常灵活(也适用于非标准数值类型,IIRC)。

两阶段解析

您可以使用任一 Spirit Qi 规则来指定确切的格式,并且仅当 raw[] 输入序列符合您的要求时才将其传递给标准数字解析器。

涉及更多但也是更优化的方法是使用 Spirit Lexer 对输入进行标记 - 有效地执行相同但更有效的方法。

中间立场

这里的中间立场是使用普通的 (Posix|Perl|C++11|Boost) 正则表达式来验证输入格式并将其传递给任何合适的转换(如 Boost Lexical cast,或者只是std::stringstream >> double 等)

一个示例展示了 Spirit Qi 和正则表达式在工作中的预匹配,同时解析浮点数的格式 (语言是 c++0x1):

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/regex.hpp>

namespace qi=boost::spirit::qi;
namespace phx=boost::phoenix;

bool is_ok(const std::vector<char>& raw)
{
    static const boost::regex rx(R"(-?(\d*\.\d+|\d+\.\d*))");
    return boost::regex_match(raw.begin(), raw.end(), rx);
}

template <typename Input>
    void test(const Input& input)
{
    auto f(std::begin(input)), l(std::end(input));

    double parsed = 0;
    bool ok = qi::phrase_parse(f, l, 

       // this is the parser expression
       &(+qi::char_)[ qi::_pass = phx::bind(is_ok, qi::_1) ]
       >> qi::double_, 
       // end parser expression

       qi::space, parsed);

    std::cout << "DEBUG: '" << input << "'\t"  << std::boolalpha << ok << "\t" << parsed << std::endl;
}

int main()
{
    const std::string good[]  = { ".300", "300.", "-.4", "-4." };
    const std::string wrong[] = { "", ".", "1", "-1", "-1111", "3..", "+1", "+.1", "+1.", "+1.0", "+-2.", "-+2." };

    for (auto& input : good)
        test(input);

    for (auto& input : wrong)
        test(input);
}

1 使用 c++11 功能:

  • 基于范围的
  • 正则表达式规范的原始字符串文字

【讨论】:

  • 谢谢,中间的方法似乎是最明智的。
  • @Tibor:我终于来做样本了。我最终用 Spirit 做了“中间地带”的事情,所以你对它的外观有所了解。如果您的输入格式很简单,我可以想象您使用标准 iostreams :)
【解决方案3】:

整数:它们应该是 0-9 的连续数字,带或不带前导减号(不允许加号,允许前导零)

浮点数:[整数部分].[小数部分] 带或不带前导减号且不带任何千位分隔符。可以省略整个部分或小数部分(例如 0.4 或 4.),但不能同时省略

这些几乎不是“自定义格式”; stringstream 可以很好地解析它们(或者,如果您使用 BOOST,则可以使用 lexical_cast)。

#include <iostream>
#include <string>
#include <sstream>

int main( ... ) {   
    std::string s = "-1.0";
    float f = 0;        
    if( std::stringstream(s) >> f ) {
        std::cout << f;
    }
    else {
        std::cout << "No good!";
    }

    return 0;
}

【讨论】:

    猜你喜欢
    • 2021-09-25
    • 1970-01-01
    • 2013-03-15
    • 2013-12-31
    • 2015-08-23
    • 2021-12-03
    • 2014-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多