如何验证一个字符串是有效的双精度浮点数(即使它包含小数点)?

14

我整晚都在搜索一种方法来确定我的字符串值是否是有效的double,但我没有找到一种方法,同时也不会拒绝带有点号的数字...

在我的搜索中,我发现了这个链接

如何确定C ++中的字符串是否为数字?

而Charles Salvia给出的答案是:

bool is_number(const std::string& s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && std::isdigit(*it)) ++it;
return !s.empty() && it == s.end();
}

这适用于任何没有小数点的数字,但带有小数点的数字会被拒绝...


可能是C ++ IsFloat函数的重复内容。 - Paweł Stawarz
9个回答

16
你可以使用 strtod函数:
#include <cstdlib>

bool is_number(const std::string& s)
{
    char* end = nullptr;
    double val = strtod(s.c_str(), &end);
    return end != s.c_str() && *end == '\0' && val != HUGE_VAL;
}

你可能会想要像这样使用std::stod

bool is_number(const std::string& s)
{
    try
    {
        std::stod(s);
    }
    catch(...)
    {
        return false;
    }
    return true;
}

但这可能相当低效,例如参见零成本异常


没错,我实际上错过了OP寻找“有效双精度”行为而不是“有效数字”的部分。现在已经修复了,谢谢! - emlai
效率方面怎么样?一个错误的返回值会伴随着与catch处理程序相关的巨大运行时低效。这应该是一个严重的问题,因为这个函数可能会被频繁调用。 - Yannis
即使编译器不使用零成本异常,运行时命中catch也意味着展开堆栈。相关成本是巨大的(我不记得确切的乘数)。 - Yannis
不确定,编译器可能能够优化展开,因为异常会立即被捕获。这在很大程度上取决于实现。无论如何,我已经更新了答案。 - emlai
1
@Ð. 修复了解决方案,使其对那些情况返回false。 - emlai
显示剩余3条评论

6
为什么不直接使用istringstream?
#include <sstream>

bool is_numeric (std::string const & str) 
{
    auto result = double();
    auto i = std::istringstream(str);

    i >> result;      

    return !i.fail() && i.eof();
}

1
只要删除 i >> std::ws; 这一行,这段代码就可以正常工作。否则,它会在基本输入(如“67.12345”)上失败。 - Ð..
1
谢谢。我已经修复了。我认为我们可以假设输入在任何情况下都已经去除了空格。 - Robinson
1
为什么要使用auto result = double();而不是double result;或者double result = {}; - Troyseph
我更喜欢AAA(几乎总是自动)。 - Robinson

4
你可以使用std::istringstream()。如果数字后面没有非数字字符,则该函数不会设置eof()标志,从而告诉你这是一个有效的数字。
bool is_number(const std::string& s)
{
    long double ld;
    return((std::istringstream(s) >> ld >> std::ws).eof());
}

int main()
{
    std::cout << std::boolalpha << is_number("   3.14") << '\n';
    std::cout << std::boolalpha << is_number("   3.14x") << '\n';
    std::cout << std::boolalpha << is_number("   3.14 ") << '\n';
}

输出:

true
false
true

模板版本: 用于测试特定类型

template<typename Numeric>
bool is_number(const std::string& s)
{
    Numeric n;
    return((std::istringstream(s) >> n >> std::ws).eof());
}

int main()
{
    std::cout << std::boolalpha << is_number<int>("314") << '\n';
    std::cout << std::boolalpha << is_number<int>("3.14") << '\n';
    std::cout << std::boolalpha << is_number<float>("   3.14") << '\n';
    std::cout << std::boolalpha << is_number<double>("   3.14x") << '\n';
    std::cout << std::boolalpha << is_number<long double>("   3.14 ") << '\n';
}

输出:

true
false
true
false
true

2

您还可以计算字符串中包含的点数。如果此数字小于等于1且所有其他字符都是数字,则您的字符串是有效的双精度。

bool isnumber(const string& s)
{
    int nb_point=0;
    for (int i=0; i<s.length();i++)
    {
         if (s[i]=='.')
         {
              nb_point++;
         }
         else if (!isdigit(s[i])
         {
              return false;
         }
    }
    if (nb_point<=1)
    {
          return true;
    }
    else
    {
          return false;
    }
}

如果您知道如何处理正则表达式,也可以使用它来实现...


负数怎么办? - Ð..

0

再添加一个检查 c == '.'

bool is_number(const std::string& s)
{
    return !s.empty() && std::find_if(s.begin(), 
    s.end(), [](char c) { return !(std::isdigit(c) || c == '.');  }) == s.end();
}

你可以通过以下方式使代码更易读:

bool is_number(const std::string& s)
{
    int dotCount = 0;
    if (s.empty())
       return false;

    for (char c : s )
    {
       if ( !(std::isdigit(c) || c == '.' ) && dotCount > 1 )
       {
          return false;
       }
       dotCount += (c == '.');
    }

    return true;
}

@CinCout,是的,它会。很好的发现。 - R Sahu
dotCount > 1 时,为什么不在循环内使用 return - CinCout
@CinCout,可以这样做。如果我们遇到这种情况的次数相对于没有遇到的次数很少,那么这样做会使代码变得比实际使用所需的更加复杂,而收益却不大。 - R Sahu
我对是否会过度优化感到怀疑,因此提出这个问题。 - CinCout

0
确保数字中最多只有一个点。
bool is_number(const std::string& s)
{
    if (s.empty())
       return false;

    bool sawDot = false;
    for (char c : s )
    {
       if ( !(std::isdigit(c) || (c == '.' && !sawDot) ) )
          return false;
       sawDot = sawDot || (c == '.');
    }

    return true;
}

这是否涵盖负数? - Peyman
@Peyman 不,这对负数不起作用。 - chmike

0

我非常强烈反对在如此低级别的操作中使用try-catch方法。相反,我自己使用以下方法:

bool const hasDoubleRepresentation( std::string const & str, double & d ) const {
std::stringstream sstr(str);
return !((sstr >> std::noskipws >> d).rdstate() ^ std::ios_base::eofbit);}

以上有一个优点,如果它返回 true,则还将 d 设置为 str 中包含的数字。如果不需要此附加功能,则可以将 d 作为输入参数删除。 还要注意 noskipws(不跳过空格)的存在会导致如果 str 是类似于“3.14”的带空格数字,则返回 false。

0

关于这个问题已经有一些回答,所以我觉得我自己也可以添加一下。如果您能够使用C++17,那么使用charconv可能会是更高效的方法。我还没有对此进行基准测试,但对于大多数字符串转换应用程序而言,charconv非常有效。该解决方案还需要编译器支持charconv与double之间的转换。VC++支持此功能;我不确定其他编译器是否已实现了标准库的这个方面。

#include <string>
#include <charconv>

bool string_is_number(const std::string& str)
{
    if (str.empty()) return false;
    double result;
    auto[ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
    return (ec == std::errc());
}

当成功转换时,std::from_chars 返回 std::errc()。本质上,这只是将 from_chars 包装起来并丢弃生成的双精度浮点数。根据您的用例,获取错误值和双精度浮点数可能更有优势(假设字符串稍后将被转换为数字),但此时使用单独的 from_chars 更合理。


-2

这是我的解决方案:

bool isNumber(string s)
{
    for (int i = 0; i < s.length(); i++)
        if (isdigit(s[i]) == false && s[i] != '.')
            return false;

    return true;
}

对我来说它正常工作,你可以试试!


这仅适用于最简单的情况。如果我理解正确,它将无法捕获“2.7.3”存在问题(它不是有效的浮点数),或者“1.0e-19”存在问题(它是有效的浮点数)。 - markgalassi
这个函数循环遍历一个字符串,我认为这不是一个很好的选择。可以添加一个基础条件或替换循环。 - laughing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接