测试 C++98 字符串是否为科学计数法的数字

Test if C++98 string is a number in scientific notation

我有一个 string 变量,它可以是以下 3 种事物中的一种:

  1. 一个数
  2. 科学记数法中的数字
  3. 文字

在情况 1 和情况 3 中,我不想做任何事情并传递数据。但在案例 2 中,我需要将其转换为常规数字。如果我总是简单地将变量转换为常规数字,那么当它包含实际文本时,它就会变为“0”。所以我需要知道字符串是否是科学计数法中的数字。明显的、肮脏的答案是这样的算法:

只要看到数字就遍历字符串。如果第一个遇到的字母是 "e" 或 "E" 并且后面跟着“+”或“-”,或者严格来说更多的数字,那么它就是一个科学计数法的数字,否则它只是一个普通数字或文字。

但我认为在 C++98 中有更好的方法来执行此操作(无需提升)。有什么内置方法可以提供帮助吗?即使它只是使用 try/catch.

编辑问题已关闭,因为它被假定为家庭作业。这不是家庭作业。因此,应该重新开放。另外,澄清一下,由于技术限制,我不得不使用 C++98。

我根据我最初的想法绘制了一个拟议的有限状态自动机("other" 表示所有未针对该给定状态指定的字符)。我相信这是正确的。

应该接受的一些示例输入:

1.453e-8
0.05843E5
8.43e6
5.2342E-7

一些应该失败的示例输入:

hello
03HX_12
8432
8432E
e-8
fail-83e1

你的自动机的主要问题是在 specification/requirements 方面:它要求小数点两边都有一个或多个数字,拒绝像这样的输入:

.123
.123E3
123.
123.E+3

这些应该被拒绝并不是很明显;一些编程语言允许这些形式。不过,拒绝完全没有尾数的东西可能是个好主意:

.
.E+03

如果这是未达到要求,而不是有意为之,则必须调整您的状态机。因为现在元素是可选的,所以修复状态机的最简单方法是让它成为非确定性的(NFA 图)。那只会带来不必要的困难。由于无论如何您最终都会编写代码,因此使用 ad hoc 过程代码来处理这个问题是最简单的。

ad hoc 过程代码相对于使用传统自动机进行正式处理的优势在于前瞻性:它可以在不消耗输入的情况下窥视下一个字符,就像自动机一样并且基于前瞻性,它可以决定是否使用字符,独立于多个代码路径之间的转换。也就是说,伪代码:

have_digits_flag = false

while (string begins with a digit character) {
   have_digits_flag = true
   consume digit character 
}

if (!string begins with a decimal point)
   goto bad;
consume decimal point

while (string begins with digit) {
  consume digit
  have_digits_flag = true;
}

if (!have_digits_flag)
  goto bad; // we scanned just a decimal point not flanked by digits!

if (string begins with e or E) {
   consume character
   if (string begins with + or -)
     consume character
   if (!string begins with digit)
     goto bad;
   while (string begins with digit)
     consume character
}

if (string is empty)
  return true;

// oops, trailing junk

bad:
  return false;

"string begins with" 操作显然必须是安全的,以防止字符串为空。空字符串根本不满足任何字符的 "string begins with" 谓词。

如何实施 "consume character" 取决于您。您可以从字面上从 std::string 对象中删除一个字符,或者通过它移动一个指示输入位置的迭代器。

这种方法的缺点是效率不高。这在这里可能无关紧要,但通常 ad hoc 代码会遇到这样的问题,即它会针对先行测试多个案例。在一个复杂的模式中,这些可能很多。 table 驱动的自动机永远不必再次查看任何输入符号。

JSON 使用一个非常好的状态机来解析数字。它并不过分严格,但不接受像“e”或“-.e2”这样的垃圾。

是:

  • “-”,或者,
  • 没有,

其次是

  • '0',或者,
  • (一个数字 [1-9],后跟零个或多个 [0-9])

其次是

  • ('.'后跟一个或多个[0-9]), 或者,

其次是

  • (('e' 或 'E'),后跟 ('+' 或 '-' 或无),后跟零个或多个 [0-9]),或者,

如果您想查看更正式的格式说明,请参阅 RFC 7159, Section 6

number = [ minus ] int [ frac ] [ exp ]
decimal-point = %x2E       ; .
digit1-9 = %x31-39         ; 1-9
e = %x65 / %x45            ; e E
exp = e [ minus / plus ] 1*DIGIT
frac = decimal-point 1*DIGIT
int = zero / ( digit1-9 *DIGIT )
minus = %x2D               ; -
plus = %x2B                ; +
zero = %x30                ; 0

这就是我在 JSON 数字解析器中所做的(我支持允许全范围 64 位整数的扩展格式,JSON 规范说整数不能可靠地位于范围之外 - 2^53+1 到 2^53-1):

template<typename InputIt,
         typename V = typename 
            std::iterator_traits<InputIt>::value_type>
static InputIt extractNumber(Variant& result, InputIt st, InputIt en);

template<typename InputIt, typename V>
InputIt JsonParser::extractNumber(Variant& result, InputIt st, InputIt en)
{
    if (st == en)
        parseError("Expected number at end of input");

    std::vector<V> text;

    auto accept = [&] {
        if (st == en)
            parseError("Expected number at end of input");
        text.emplace_back(*st++);
    };

    // -?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?
    // A    B C              D            E

    bool isFloatingPoint = false;

    if (*st == '-')
    {
        // A
        accept();
    }

    if (*st == '0')
    {
        // B
        accept();
    }
    else if (std::isdigit(*st, cLocale))
    {
        // C
        do
        {
            accept();
        }
        while (st != en && std::isdigit(*st, cLocale));
    }
    else
    {
        parseError("Invalid number");
    }

    if (st != en && *st == '.')
    {
        accept();

        isFloatingPoint = true;

        // D
        while (st != en && std::isdigit(*st, cLocale))
            accept();
    }

    if (st != en && (*st == 'E' || *st == 'e'))
    {
        isFloatingPoint = true;

        // E
        accept();

        if (st != en && (*st == '+' || *st == '-'))
            accept();

        if (st == en || !std::isdigit(*st, cLocale))
            parseError("Invalid number");

        while (st != en && std::isdigit(*st, cLocale))
            accept();
    }

    text.emplace_back(0);

    if (isFloatingPoint)
        result.assign(std::atof(text.data()));
    else
        result.assign(std::int64_t(std::atoll(text.data())));

    return st;
}

我不得不稍微调整一下,因为周围的实现确保 st 在输入时不等于 en,最初我断言。它通过了我的单元测试。

bool is_valid(std::string src){
    std::stringstream ss;
    ss << src;
    double d=0;
    ss >> d; 
    if (ss){
       return true;
    }
    else{
       return false;
    }
}

我有一个简单的解决办法。使用C++流测试字符串是否为数字。

我最终根据我在问题中绘制的 FSA 编写了一个函数。加上一些基于 koz 的信息和建议的调整(谢谢),并减去一些确定性,因为它是代码,我可以走捷径:)。它体积庞大,但易于理解且有效。请参阅代码下方的示例测试用例。

bool is_scientific_notation(string input) {
    int state = 0;
    for (string::size_type i = 0; i < input.size(); i++) {
        char character = input.at(i);
        //cout << character << endl;
        switch(state) {
            case 0: {
                // state 0: accept on '. or' '-' or digit
                if (character == '.') {
                    state = 3;
                } else if (character == '-') {
                    state = 1;
                } else if (isdigit(character)) {
                    state = 2;
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 1: {
                // state 1: accept on '. or digit
                if (character == '.') {
                    state = 3;
                } else if (isdigit(character)) {
                    state = 2;
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 2: {
                // state 2: accept on '.' or 'e' or 'E' digit
                if (character == '.') {
                    state = 4;
                } else if ((character == 'e') || (character == 'E')) {
                    state = 5;
                } else if (isdigit(character)) {
                    state = 2;
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 3: {
                // state 3: accept on digit
                if (isdigit(character)) {
                    state = 4;  
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 4: {
                // state 4: accept on 'e' or 'E' or digit
                if ((character == 'e') || (character == 'E')) {
                    state = 5;
                } else if (isdigit(character)) {
                    state = 4;  
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 5: {
                // state 5: accept on '+' or '-' or digit
                if ((character == '+') || (character == '-')) {
                    state = 6;
                } else if (isdigit(character)) {
                    state = 6;  
                } else {
                    goto reject; // reject 
                }
                break;
            }
            case 6: {
                // state 6: accept on digit
                if (isdigit(character)) {
                    state = 6;  
                } else {
                    goto reject; // reject 
                }
                break;
            }
        }
    }
    if (state == 6) {
        return true;
    } else {
        reject:
            return false;
    }
}

测试:

// is_scientific_notation should return true
cout << ((is_scientific_notation("269E-9")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("269E9")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("269e-9")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("1.453e-8")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("8.43e+6")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("5.2342E-7")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation(".2342E-7")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("8.e+2")) ? ("pass") : ("fail")) << endl;
cout << ((is_scientific_notation("-853.4E-2")) ? ("pass") : ("fail")) << endl;

// is_scientific_notation should return false
cout << ((is_scientific_notation("hello")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("03HX_12")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("8432")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("8432E")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("fail-83e1")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation(".e8")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("E-8")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("2e.2")) ? ("fail") : ("pass")) << endl;
cout << ((is_scientific_notation("-E3")) ? ("fail") : ("pass")) << endl;

输出:

pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass
pass