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 →