测试 C++98 字符串是否为科学计数法的数字
Test if C++98 string is a number in scientific notation
我有一个 string
变量,它可以是以下 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
我有一个 string
变量,它可以是以下 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