Numbers
Numeric regex covers a lot of everyday validation: integers, decimals, currency, signed values, scientific notation, percentages. The trick is matching the SHAPE you actually want and parsing real numbers with the language’s built-in (parseFloat, Number) once shape is confirmed.
Integer, decimal, currency, scientific
EXAMPLE
// 1) Whole non-negative integer
const INT_RE = /^\d+$/;
INT_RE.test('0'); // true
INT_RE.test('123'); // true
INT_RE.test('-1'); // false
INT_RE.test('1.5'); // false
INT_RE.test(''); // false
// 2) Optional sign + integer
const SIGNED_INT_RE = /^[+-]?\d+$/;
SIGNED_INT_RE.test('-42'); // true
SIGNED_INT_RE.test('+0'); // true
// 3) Decimal number — integer or with fractional part
const DECIMAL_RE = /^[+-]?\d+(?:\.\d+)?$/;
DECIMAL_RE.test('3.14'); // true
DECIMAL_RE.test('-0.5'); // true
DECIMAL_RE.test('.5'); // false (no leading digit)
DECIMAL_RE.test('3.'); // false (no trailing digit)
// More forgiving — leading or trailing optional
const FLEX_DECIMAL = /^[+-]?(?:\d+\.?\d*|\.\d+)$/;
FLEX_DECIMAL.test('3.14');
FLEX_DECIMAL.test('.5');
FLEX_DECIMAL.test('3.');
// 4) Currency — 2 decimal places, optional commas, optional sign
const USD_RE = /^[+-]?\\$?\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?$/;
USD_RE.test('$1,234.56'); // true
USD_RE.test('1234.56'); // true
USD_RE.test('1234'); // true
USD_RE.test('1,234'); // true
USD_RE.test('1,2'); // false
// CAVEAT: locales differ. Europe uses '1.234,56'; better to strip + use Intl.NumberFormat to parse.
// 5) Percentage
const PCT_RE = /^[+-]?\d+(?:\.\d+)?%$/;
PCT_RE.test('25%'); // true
PCT_RE.test('-3.5%'); // true
// 6) Scientific notation
const SCI_RE = /^[+-]?\d+(?:\.\d+)?[eE][+-]?\d+$/;
SCI_RE.test('1.5e3'); // true
SCI_RE.test('-2.5E-10'); // true
SCI_RE.test('1e0'); // true
// 7) Hex / binary / octal
const HEX_RE = /^0x[0-9a-fA-F]+$/;
const BIN_RE = /^0b[01]+$/;
const OCT_RE = /^0o[0-7]+$/;
HEX_RE.test('0xff'); // true
BIN_RE.test('0b1010'); // true
// 8) Integer range
const BYTE_RE = /^(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
BYTE_RE.test('255'); // true
BYTE_RE.test('256'); // false
// IPv4 octet — combine 4x:
const IPV4_RE = /^(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)$/;
// 9) Phone number digits only (10-15 digits, optional +)
const PHONE_RE = /^\+?\d{10,15}$/;
PHONE_RE.test('+61412345678'); // true
PHONE_RE.test('0412345678'); // false (no + but still digits) — adjust if leading 0 allowed
// 10) Fixed-precision (e.g. 8-digit ID)
const ID_RE = /^\d{8}$/;
ID_RE.test('12345678'); // true
ID_RE.test('1234567'); // false (7 digits)
// 11) Numbers with thousands separators
const SEP_NUM = /^\d{1,3}(?:,\d{3})*(?:\.\d+)?$/;
SEP_NUM.test('1,234,567.89'); // true
SEP_NUM.test('1234567.89'); // false (no commas — write a forgiving pattern)
// More forgiving — accept either
const FLEX_NUM = /^\d{1,3}(?:[,]\d{3})*(?:\.\d+)?$|^\d+(?:\.\d+)?$/;
FLEX_NUM.test('1234567.89'); // true
FLEX_NUM.test('1,234,567.89'); // true
// 12) Real-world: validate + parse
function parseAmount(input) {
const cleaned = input.replace(/[$,\s]/g, '');
if (!FLEX_DECIMAL.test(cleaned)) return null;
const n = parseFloat(cleaned);
return Number.isFinite(n) ? n : null;
}
parseAmount('$1,234.56'); // 1234.56
parseAmount('1234'); // 1234
parseAmount('abc'); // null
// 13) Avoid relying on regex for safety on financial math
// • JavaScript number precision: 0.1 + 0.2 !== 0.3
// • Use BigInt for cents: 100 * dollars + cents → BigInt
// • Use libraries like dinero.js or money.js for arithmetic
const dollarsCents = '1234.56';
const cents = BigInt(dollarsCents.replace('.', '')); // 123456n — safe big integer
// 14) Locale-aware parsing — better than regex for user input
const formatter = new Intl.NumberFormat('en-AU', { style: 'currency', currency: 'AUD' });
const groupingChar = (new Intl.NumberFormat('en-AU').formatToParts(1000).find((p) => p.type === 'group')?.value ?? ',');
const decimalChar = (new Intl.NumberFormat('en-AU').formatToParts(0.1).find((p) => p.type === 'decimal')?.value ?? '.');
function parseLocalised(input) {
const clean = input.replace(new RegExp(`[\\\\\\\\\$\\\\\\\${groupingChar}]`, 'g'), '').replace(decimalChar, '.');
return parseFloat(clean);
}
parseLocalised('1,234.56'); // 1234.56 (en-AU)
parseLocalised('1.234,56'); // works in de-DE configuration
// 15) When regex is the right tool
// • Quick shape check (UI form validation)
// • Extract numbers from free text ('total: $42.99 ' → ['$42.99'])
// • Log parsing (counts, durations)
//
// When regex is the WRONG tool
// • Real money math — use proper decimal/BigInt libraries
// • Cross-locale parsing — use Intl
// • Rigorous range validation — combine regex with numeric checks (Number.isFinite, etc.)
// 16) Common bugs
// • Forgetting ^ and $ → '\d+' matches 'foo123bar' as 123
// • Allowing '.' alone → use the FLEX_DECIMAL pattern
// • Catastrophic backtracking with nested quantifiers — keep patterns simple
// • Trusting regex for range (1-100) — match digits then test numerically
// • Negative numbers without explicit sign group → false negative
// • Localisation: '1,234.56' (en-US) vs '1.234,56' (de-DE) — strip and re-parse with Intl
// • Scientific notation skipped → some users expect '1e3'; document or accept
// • Leading zeros (012345) interpreted as octal in some parsers — use parseInt(s, 10) explicitly
Why it matters
Use regex for shape (^\d+$, ^[+-]?\d+(?:\.\d+)?$, etc.) and a real numeric parser for value — then range-check numerically. For currency and financial math, stay in BigInt cents and reach for Intl.NumberFormat for locale-aware parsing — regex alone can’t handle European decimal commas.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?$/ // signed decimal with optional exponentTry it Yourself »
Discussion
Loading…