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 (C11, 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 <ctype.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
/* Per-country IBAN lengths (ISO 13616) — a representative subset. */
static const struct {
const char *cc;
int len;
} IBAN_LENGTHS[] = {
{"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},
};
#define IBAN_LENGTHS_COUNT (sizeof IBAN_LENGTHS / sizeof IBAN_LENGTHS[0])
/* Result of validation — nullable fields are empty string / NULL / -1. */
typedef struct {
char input[256];
char cleaned[64];
char country_code[3]; /* "" when the head is not 2 uppercase letters */
bool valid;
bool checksum_ok;
bool length_ok;
int expected_length; /* -1 when the country is unknown */
char formatted[80];
char error_buf[80];
const char *error; /* NULL when valid; may point into error_buf */
} IbanInfo;
static int iban_length_for(const char *cc)
{
for (size_t i = 0; i < IBAN_LENGTHS_COUNT; i++)
if (strcmp(IBAN_LENGTHS[i].cc, cc) == 0)
return IBAN_LENGTHS[i].len;
return -1;
}
static bool is_upper_alpha(unsigned char c) { return c >= 'A' && c <= 'Z'; }
static bool is_digit_char(unsigned char c) { return c >= '0' && c <= '9'; }
/* ISO 13616 mod-97 checksum over a CLEANED iban (uppercase, no spaces). */
static bool mod97_check(const char *cleaned)
{
size_t n = strlen(cleaned);
if (n < 4)
return false;
/* Rearranged = cleaned[4..] ++ cleaned[..4] (country + check moved to
* the end); index (i + 4) % n walks that rotation in place. */
unsigned rem = 0;
for (size_t i = 0; i < n; i++) {
unsigned char ch = (unsigned char)cleaned[(i + 4) % n];
unsigned value;
int digits;
if (is_digit_char(ch)) {
value = ch - '0';
digits = 1;
} else if (is_upper_alpha(ch)) {
value = ch - '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. */
static void format_groups(const char *cleaned, char *out, size_t cap)
{
size_t n = strlen(cleaned), o = 0;
for (size_t i = 0; i < n; i++) {
if (i > 0 && i % 4 == 0 && o + 1 < cap)
out[o++] = ' ';
if (o + 1 < cap)
out[o++] = cleaned[i];
}
out[o] = '\0';
}
/* Validate an IBAN. Never fails — all failures are reported via `error`. */
static IbanInfo validate_iban(const char *input)
{
IbanInfo info = {0};
info.expected_length = -1;
snprintf(info.input, sizeof info.input, "%s", input ? input : "");
/* Uppercase and strip whitespace/dashes. */
size_t o = 0;
for (const char *p = info.input; *p; p++) {
unsigned char ch = (unsigned char)*p;
if (ch == ' ' || ch == '-' || ch == '\t' || ch == '\n' || ch == '\r')
continue;
if (o + 1 < sizeof info.cleaned)
info.cleaned[o++] = (char)toupper(ch);
}
info.cleaned[o] = '\0';
size_t n = strlen(info.cleaned);
if (n >= 2 && is_upper_alpha((unsigned char)info.cleaned[0]) &&
is_upper_alpha((unsigned char)info.cleaned[1])) {
info.country_code[0] = info.cleaned[0];
info.country_code[1] = info.cleaned[1];
info.expected_length = iban_length_for(info.country_code);
}
format_groups(info.cleaned, info.formatted, sizeof info.formatted);
/* Structure: 2 letters, 2 digits, then 1..30 alnum, total len 5..34. */
bool struct_ok = n >= 5 && n <= 34
&& is_upper_alpha((unsigned char)info.cleaned[0])
&& is_upper_alpha((unsigned char)info.cleaned[1])
&& is_digit_char((unsigned char)info.cleaned[2])
&& is_digit_char((unsigned char)info.cleaned[3]);
if (struct_ok) {
for (size_t i = 4; i < n; i++) {
unsigned char ch = (unsigned char)info.cleaned[i];
if (!is_upper_alpha(ch) && !is_digit_char(ch)) {
struct_ok = false;
break;
}
}
}
if (!struct_ok) {
info.error = "Invalid IBAN format.";
return info;
}
info.checksum_ok = mod97_check(info.cleaned);
info.length_ok = info.expected_length < 0
|| (int)n == info.expected_length;
if (!info.length_ok) {
snprintf(info.error_buf, sizeof info.error_buf,
"Length should be %d for %s.", info.expected_length,
info.country_code);
info.error = info.error_buf;
return info;
}
if (!info.checksum_ok) {
info.error = "Checksum failed.";
return info;
}
info.valid = true;
return info;
}
int main(void)
{
/* GB82 WEST 1234 5698 7654 32 — a known-good reference IBAN. */
IbanInfo r = validate_iban("GB82WEST12345698765432");
printf("valid=%d error=%s\n", r.valid, r.error ? r.error : "(none)");
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 →