Matching Numbers with Regular Expressions
Number matching sounds simple until you realise how many shapes a "number" can take: 42, -3.14, 1e10, 0x1A, 1,000, 007, 3/4. Regex can handle most of these — but only if you pick the pattern that matches your exact definition. Below are the patterns developers reach for every day, with clear rules about what they accept and reject.
The Core Patterns
Digits only (no sign, no decimal)
/^\d+$/ // one or more digits
/^[0-9]+$/ // identical, explicit character class
/^\d{5}$/ // exactly 5 digits — good for US ZIP codes
/^\d{3,10}$/ // 3 to 10 digits — good for phone/OTP\d is a shortcut for [0-9] in most regex flavours (ECMAScript, PCRE, Python, .NET). In JavaScript without the u flag, \d also matches non-ASCII Unicode digits — usually not what you want. Prefer [0-9] when you need strict ASCII digits only.
Signed integers
/^-?\d+$/ // optional minus sign
/^[+-]?\d+$/ // optional plus OR minus
/^(0|[1-9]\d*)$/ // no leading zeros allowed (0, 42, 1000 — NOT 007)Decimal numbers
/^-?\d+(\.\d+)?$/ // integer OR decimal
/^-?\d+\.\d+$/ // decimal required (rejects "42")
/^-?(\d+\.?\d*|\.\d+)$/ // also allow ".5" (no leading digit)
/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/ // scientific notation (1e10, 3.14e-2)Numbers with thousands separators
/^\d{1,3}(,\d{3})*(\.\d+)?$/ // 1,000 1,234,567.89
/^\d{1,3}(\.\d{3})*(,\d+)?$/ // European: 1.234,56Language-Specific Usage
JavaScript
function isNumber(str) {
return /^-?\d+(\.\d+)?$/.test(str);
}
isNumber('42'); // true
isNumber('-3.14'); // true
isNumber('1e10'); // false (add scientific if needed)
isNumber(' 42 '); // false (whitespace not allowed)
isNumber(''); // false
// Extract all numbers from a string
'Price: $42.99 and $19.50'.match(/-?\d+(\.\d+)?/g);
// → ['42.99', '19.50']Python
import re
NUMBER = re.compile(r'^-?\d+(\.\d+)?$')
def is_number(s: str) -> bool:
return bool(NUMBER.match(s))
is_number('42') # True
is_number('-3.14') # True
# Extract every number from a string
re.findall(r'-?\d+(?:\.\d+)?', 'Q1 revenue: $1.2M, Q2: $1.8M')
# → ['1.2', '1.8']PHP
function isNumber(string $s): bool {
return (bool) preg_match('/^-?\d+(\.\d+)?$/', $s);
}
// PHP also has is_numeric() which handles scientific + hex
is_numeric('1e10'); // true
is_numeric('0x1A'); // false (in PHP 7+)Common Pitfalls
Leading zeros
/^\d+$/ accepts 007. If leading zeros are illegal (bank account numbers, some ID formats) use /^(0|[1-9]\d*)$/ — either a single zero or a non-zero digit followed by any digits.
Empty strings match zero-length patterns
/^\d*$/ with a star quantifier accepts the empty string. If you require at least one digit, always use + not *.
Regex is bad at numeric ranges
Matching "numbers from 1 to 100" with pure regex requires (100|[1-9]?[0-9]) and it gets ugly fast. For any real range check: match the digits with regex, then convert to a number in your code and compare — const n = parseInt(str); if (n >= 1 && n <= 100) ….
Locale-specific number formats
The US writes 1,234.56. Germany writes 1.234,56. France writes 1 234,56. Never assume one format — either normalise the input first or use multiple patterns.
Number Format Cheatsheet
| Goal | Pattern | Matches |
|---|---|---|
| Digits only | /^\d+$/ | 42, 007, 100 |
| Integer | /^-?\d+$/ | 42, -100, 0 |
| Decimal | /^-?\d+(\.\d+)?$/ | 3.14, -0.5, 42 |
| Scientific | /^-?\d+(\.\d+)?e[+-]?\d+$/i | 1e10, 3.14E-2 |
| Percentage | /^\d+(\.\d+)?%$/ | 50%, 3.14% |
Testing Your Number Regex
Use the live Regex Tester above with these test strings:
- Match:
42,-100,3.14,0 - Match (with scientific):
1e10,3.14e-2 - Reject:
abc,1,000,3.14.15, empty string - Reject:
NaN,Infinity,0x1A - Edge:
.5and5.— accepted by some patterns, rejected by others
Common Mistakes When Writing Number Regex
Even experienced developers stumble on a few recurring number-regex pitfalls. Watching for these upfront saves hours of debugging later:
- Forgetting anchors. Without
^and$, the pattern/\d+/will happily match the123insideabc123def. If your goal is validation (not extraction), always anchor. - Treating
\das ASCII only. In modern JavaScript, Python, and Java,\dcan match Unicode digits like Arabic-Indic٠-٩when Unicode mode is on. Use[0-9]explicitly if you strictly want ASCII digits — it's more portable and unambiguous across engines. - Ignoring locale-formatted numbers. Real user input often contains thousands separators like
1,000,000or European commas as decimals (3,14). Strip separators before regex validation, or explicitly account for them in your pattern. - Missing the sign group. A pattern like
/^\d+$/rejects-42. Add-?at the start if negative numbers are valid input for your use case. - Over-permissive decimal patterns.
/^\d*\.?\d*$/matches an empty string and a lone dot. Prefer/^\d+(\.\d+)?$/to require at least one digit.
Performance Notes
Number regexes are among the fastest patterns because character classes like [0-9] and \d are highly optimized in every engine. Even validating a million strings against /^-?\d+$/ runs in tens of milliseconds in Node.js or Python. The one caveat is nested quantifiers — patterns like (\d+)+ can trigger catastrophic backtracking. If you find yourself writing nested repeats around digit groups, refactor to use a single quantifier or a non-capturing group with an atomic pattern where the engine supports it.