Skip to content

IBAN Validator — C++ source

Validate International Bank Account Numbers (IBAN) with the mod-97 checksum, verify the country-specific length, and format the result. 100% client-side, no network.

This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.

// iban-validator — pure IBAN validation logic (ISO 13616 mod-97 checksum).
//
// Language: C++ (C++17, standard library only)
// Source:   CosmoDev polyglot showcase port of the iban-validator tool,
//           ported from src/lib/iban.ts (the canonical TypeScript
//           implementation).
// License:  display source — part of CosmoDev's polyglot tool pages
//
// Normalizes the input (uppercase, strip whitespace/dashes), checks the
// structure (country code + 2 check digits + 1-30 BBAN chars), verifies
// the per-country length, then runs the ISO 13616 mod-97 checksum: move
// the first 4 chars to the end, map A=10..Z=35, and confirm the
// resulting integer is congruent to 1 mod 97. The remainder is folded
// one decimal digit at a time (it stays < 97), so no big-integer or
// regex machinery is required.

#include <cctype>
#include <iostream>
#include <map>
#include <optional>
#include <string>

// Per-country IBAN lengths (ISO 13616) — a representative subset.
const std::map<std::string, int>& iban_lengths()
{
    static const std::map<std::string, int> m = {
        {"AL", 28}, {"AD", 24}, {"AT", 20}, {"AZ", 28}, {"BH", 22}, {"BY", 28},
        {"BE", 16}, {"BA", 20}, {"BR", 29}, {"BG", 22}, {"CR", 22}, {"HR", 21},
        {"CY", 28}, {"CZ", 24}, {"DK", 18}, {"DO", 28}, {"EE", 20}, {"FO", 18},
        {"FI", 18}, {"FR", 27}, {"GE", 22}, {"DE", 22}, {"GI", 23}, {"GR", 27},
        {"GL", 18}, {"GT", 28}, {"HU", 28}, {"IS", 26}, {"IE", 22}, {"IL", 23},
        {"IT", 27}, {"JO", 30}, {"KZ", 20}, {"XK", 20}, {"KW", 30}, {"LV", 21},
        {"LB", 28}, {"LI", 21}, {"LT", 20}, {"LU", 20}, {"MK", 19}, {"MT", 31},
        {"MR", 27}, {"MU", 30}, {"MC", 27}, {"MD", 24}, {"ME", 22}, {"NL", 18},
        {"NO", 15}, {"PK", 24}, {"PS", 29}, {"PL", 28}, {"PT", 25}, {"QA", 29},
        {"RO", 24}, {"LC", 32}, {"SM", 27}, {"ST", 25}, {"SA", 24}, {"RS", 22},
        {"SC", 31}, {"SK", 24}, {"SI", 19}, {"SG", 19}, {"ES", 24}, {"SE", 24},
        {"CH", 21}, {"TL", 23}, {"TN", 24}, {"TR", 26}, {"UA", 29}, {"AE", 23},
        {"GB", 22}, {"VG", 24},
    };
    return m;
}

// Result of validation. std::optional fields mirror the TS nullable fields.
struct IbanInfo {
    std::string input;
    std::string cleaned;
    std::optional<std::string> country_code; // nullopt when head is not 2 letters
    bool valid = false;
    bool checksum_ok = false;
    bool length_ok = false;
    std::optional<int> expected_length; // nullopt when the country is unknown
    std::string formatted;
    std::optional<std::string> error; // nullopt when valid
};

static bool is_upper_alpha(char ch)
{
    unsigned char c = static_cast<unsigned char>(ch);
    return c >= 'A' && c <= 'Z';
}

static bool is_digit_char(char ch)
{
    unsigned char c = static_cast<unsigned char>(ch);
    return c >= '0' && c <= '9';
}

// ISO 13616 mod-97 checksum over a CLEANED iban (uppercase, no spaces).
bool mod97_check(const std::string& cleaned)
{
    if (cleaned.size() < 4)
        return false;
    // Move the first 4 chars (country + check) to the end.
    const std::string rearranged = cleaned.substr(4) + cleaned.substr(0, 4);
    unsigned rem = 0;
    for (char ch : rearranged) {
        unsigned char c = static_cast<unsigned char>(ch);
        unsigned value;
        int digits;
        if (is_digit_char(c)) {
            value = c - '0';
            digits = 1;
        } else if (is_upper_alpha(c)) {
            value = c - 'A' + 10; // A=10 .. Z=35
            digits = 2;
        } else {
            return false; // invalid character
        }
        // Fold one decimal digit at a time; rem stays < 97.
        if (digits == 2)
            rem = (rem * 10 + value / 10) % 97;
        rem = (rem * 10 + value % 10) % 97;
    }
    return rem == 1;
}

// Insert a space every 4 characters (last group may be short) — the
// hand-rolled equivalent of the TS regex /(.{4})(?=.)/g + trim.
std::string format_groups(const std::string& cleaned)
{
    std::string out;
    out.reserve(cleaned.size() + cleaned.size() / 4);
    for (std::size_t i = 0; i < cleaned.size(); ++i) {
        if (i > 0 && i % 4 == 0)
            out += ' ';
        out += cleaned[i];
    }
    return out;
}

// Validate an IBAN. Never throws — all failures are reported via `error`.
IbanInfo validate_iban(const std::string& input)
{
    IbanInfo info;
    info.input = input;

    // Uppercase and strip whitespace/dashes.
    for (char ch : input) {
        unsigned char c = static_cast<unsigned char>(ch);
        if (c == ' ' || c == '-' || c == '\t' || c == '\n' || c == '\r')
            continue;
        info.cleaned += static_cast<char>(std::toupper(c));
    }

    if (info.cleaned.size() >= 2 && is_upper_alpha(info.cleaned[0]) &&
        is_upper_alpha(info.cleaned[1]))
        info.country_code = info.cleaned.substr(0, 2);
    if (info.country_code) {
        auto it = iban_lengths().find(*info.country_code);
        if (it != iban_lengths().end())
            info.expected_length = it->second;
    }
    info.formatted = format_groups(info.cleaned);

    // Structure: 2 letters, 2 digits, then 1..30 alnum, total len 5..34.
    const std::string& c = info.cleaned;
    bool struct_ok = c.size() >= 5 && c.size() <= 34
        && is_upper_alpha(c[0]) && is_upper_alpha(c[1])
        && is_digit_char(c[2]) && is_digit_char(c[3]);
    if (struct_ok) {
        for (std::size_t i = 4; i < c.size(); ++i) {
            if (!is_upper_alpha(c[i]) && !is_digit_char(c[i])) {
                struct_ok = false;
                break;
            }
        }
    }
    if (!struct_ok) {
        info.error = "Invalid IBAN format.";
        return info;
    }

    info.checksum_ok = mod97_check(c);
    info.length_ok = !info.expected_length
        || static_cast<int>(c.size()) == *info.expected_length;
    if (!info.length_ok) {
        info.error = "Length should be " + std::to_string(*info.expected_length)
            + " for " + *info.country_code + ".";
        return info;
    }
    if (!info.checksum_ok) {
        info.error = "Checksum failed.";
        return info;
    }
    info.valid = true;
    return info;
}

int main()
{
    // GB82 WEST 1234 5698 7654 32 — a known-good reference IBAN.
    IbanInfo r = validate_iban("GB82WEST12345698765432");
    std::cout << "valid=" << r.valid
              << " error=" << (r.error ? *r.error : "(none)") << '\n';
    return 0;
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →