Skip to content
C++ Better Explained
Go back
How to Check if a String Is a Number in C++

How to Check if a String Is a Number in C++

Short answer: parse it and check that the whole string was consumed. The shortest correct version:

#include <sstream>

bool isNumber(const std::string& s) {
    std::istringstream ss(s);
    double d;
    return (ss >> d) && ss.eof();
}

Both halves matter — ss >> d proves it parsed, ss.eof() proves nothing was left over.


Why the Obvious Approaches Fail

isdigit on every character is the first thing most people try:

// BROKEN — too strict and too lenient at once
bool isNumber(const std::string& s) {
    for (char c : s) {
        if (!std::isdigit(static_cast<unsigned char>(c))) return false;
    }
    return !s.empty();
}

This rejects -42, 3.14 and 1e5, all of which are perfectly good numbers. It also accepts 99999999999999999999, which does not fit in an int — so you “validate” it and then overflow when you convert.

stoi on its own is the second attempt, and it is too permissive:

int n = std::stoi("12abc");   // returns 12 — no error!

stoi parses as far as it can and quietly ignores the rest. For user input that is usually wrong: someone typing 12abc made a mistake, and you should say so.

Learning C++ properly? The C++ Better Explained Ebook covers strings, input and the STL in plain English — 87 pages, just $19.

The stringstream Method

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

bool isInteger(const std::string& s) {
    std::istringstream ss(s);
    int value;
    return (ss >> value) && ss.eof();
}

bool isDecimal(const std::string& s) {
    std::istringstream ss(s);
    double value;
    return (ss >> value) && ss.eof();
}

int main() {
    std::cout << isInteger("42")    << '\n';  // 1
    std::cout << isInteger("-42")   << '\n';  // 1
    std::cout << isInteger("3.14")  << '\n';  // 0 — stops at the dot
    std::cout << isInteger("12abc") << '\n';  // 0 — leftover characters
    std::cout << isInteger("")      << '\n';  // 0
    std::cout << isDecimal("3.14")  << '\n';  // 1
    std::cout << isDecimal("1e5")   << '\n';  // 1
}

Notice you pick the rules by picking the type. Want to allow decimals? Parse into a double. Integers only? Parse into an int.

One subtlety: leading whitespace is skipped by >>, so " 42" passes. Trailing whitespace fails the eof() check, so "42 " does not. If you want both accepted, trim the string first.

The stoi Method, Done Correctly

stoi takes an optional second argument that reports how many characters it consumed:

#include <string>

bool isInteger(const std::string& s) {
    if (s.empty()) return false;
    try {
        size_t pos;
        std::stoi(s, &pos);
        return pos == s.size();     // the whole string was used
    } catch (const std::invalid_argument&) {
        return false;               // no number at all
    } catch (const std::out_of_range&) {
        return false;               // too big for int
    }
}

This version has a real advantage over stringstream: it catches out-of-range values. "99999999999999999999" throws std::out_of_range rather than silently misbehaving.

The cost is exception handling, which is slow if you are validating thousands of strings in a loop.

The Modern Method: from_chars (C++17)

#include <charconv>
#include <string>

bool isInteger(const std::string& s) {
    int value;
    auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), value);
    return ec == std::errc() && ptr == s.data() + s.size();
}

from_chars is the fastest option — no exceptions, no allocations, no locale. The return gives you an error code and a pointer to where parsing stopped, so you check both exactly as before.

Note it does not skip leading whitespace and does not accept a leading +, which makes it stricter than the alternatives. That is usually a feature for machine-readable data.

Validating User Input

In practice you usually want to combine this with a retry loop:

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

int main() {
    std::string line;
    int n;
    while (true) {
        std::cout << "Enter a whole number: ";
        std::getline(std::cin, line);

        std::istringstream ss(line);
        if ((ss >> n) && ss.eof()) break;

        std::cout << "That is not a whole number.\n";
    }
    std::cout << "Got " << n << '\n';
}

Reading whole lines with getline and parsing afterwards avoids the buffer problems that come from mixing cin >> with getline. The full treatment is in C++ input validation.

Quick Reference

MethodRejects 12abcCatches overflowSpeed
isdigit loopyesnofast but wrong
stringstreamyesnomoderate
stoi + posyesyesslow (exceptions)
from_chars (C++17)yesyesfastest

For user input, use stringstream or stoi. For parsing large files, use from_chars.


Take Your C++ Further

If you want strings, input handling and the STL explained properly rather than looked up one function at a time, the C++ Better Explained Ebook covers the fundamentals in plain English. Just $19.

👉 Get the C++ Better Explained Ebook — $19


📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
How to Remove a Character from a String in C++
Next Post
C++ String Length: size() vs length() vs strlen()

Keep Learning