The shortest regex for a positive integer: ^\d+$. For a signed integer (positive, negative, or zero): ^-?\d+$. For a decimal that may or may not have a fractional part: ^-?\d+(\.\d+)?$. The patterns get more specific from there: scientific notation, thousands-separated, percent-suffixed, currency-prefixed. Below I walk all of them with runnable code in JavaScript, Python, and PHP, engine notes, the bugs I see most often, and the case where regex stops being the right tool.
The fact you can have a different regex for "number" depending on context (form input vs CSV column vs log line) is why this question keeps coming up. The right pattern is the one that matches the exact subset your input might contain.
Quick reference
Positive integer: ^\d+$
Signed integer: ^-?\d+$
Signed (explicit +/- allowed): ^[+-]?\d+$
Non-zero positive integer: ^[1-9]\d*$
Positive decimal: ^\d+(\.\d+)?$
Signed decimal: ^-?\d+(\.\d+)?$
Scientific: ^-?\d+(\.\d+)?([eE][-+]?\d+)?$
US thousands-separated: ^-?\d{1,3}(,\d{3})*(\.\d+)?$
Currency (USD): ^\$?-?\$?\d+(,\d{3})*(\.\d{2})?$
Percent: ^-?\d+(\.\d+)?%$
The basic patterns
Positive integer: ^\d+$
Signed integer: ^-?\d+$
Signed-or-unsigned: ^[+-]?\d+$
Integer with leading 0: ^0\d+$
Non-zero positive: ^[1-9]\d*$
\dmatches a digit.\d+requires at least one.^and$anchor to the full string.-?makes the minus sign optional.[+-]?allows either explicit sign.[1-9]\d*enforces no leading zero (the first digit cannot be 0).
The non-zero-leading version is useful when you don't want 0123 interpreted as 123 (which would be the case if the application parsed it as an integer).
Decimal numbers
Positive decimal: ^\d+(\.\d+)?$
Decimal with optional integer part: ^(\d+\.\d+|\.\d+|\d+\.?)$
Signed decimal: ^-?\d+(\.\d+)?$
Decimal with fixed precision (2dp): ^-?\d+\.\d{2}$
The bare positive form accepts integers too because the fractional part is optional. The "optional integer part" form additionally accepts .5 (no leading digit) and 5. (trailing dot). Most form fields use the first form.
The fixed-precision form is useful for currency: ^-?\d+\.\d{2}$ requires exactly two decimal places.
Scientific notation
^-?\d+(\.\d+)?([eE][-+]?\d+)?$
Adds an optional exponent part: e or E, optional sign, one or more digits. Matches 1.5e10, -2.7E-9, and plain 42 (the exponent is optional).
Thousands-separated numbers (1,234,567.89)
The pattern depends on the locale's separator. For US-style commas:
^-?\d{1,3}(,\d{3})*(\.\d+)?$
\d{1,3}for the first group (1-3 digits before any comma).(,\d{3})*for zero or more comma-prefixed three-digit groups.(\.\d+)?for the optional decimal part.
This matches 1,234, 1,234,567.89, and rejects 1,2345 (wrong grouping) and ,123 (leading comma). For European locales that use . as thousands and , as decimal, swap them.
Currency and percent
USD currency: ^\$?-?\$?\d+(,\d{3})*(\.\d{2})?$
Percent: ^-?\d+(\.\d+)?%$
The currency pattern allows the $ either before the sign or after, since both $-50 and -$50 are seen in the wild.
Examples in JavaScript, Python, and PHP
JavaScript:
const patterns = {
integer: /^-?\d+$/,
decimal: /^-?\d+(\.\d+)?$/,
scientific: /^-?\d+(\.\d+)?([eE][-+]?\d+)?$/,
currency: /^\$?-?\$?\d+(,\d{3})*(\.\d{2})?$/,
};
function classify(input) {
for (const [name, pattern] of Object.entries(patterns)) {
if (pattern.test(input)) return name;
}
return null;
}
classify("42"); // 'integer'
classify("-3.14"); // 'decimal'
classify("1.5e10"); // 'scientific'
classify("$1,234.56"); // 'currency'Python:
import re
INTEGER = re.compile(r"^-?\d+$")
DECIMAL = re.compile(r"^-?\d+(\.\d+)?$")
SCIENTIFIC = re.compile(r"^-?\d+(\.\d+)?([eE][-+]?\d+)?$")
def is_number(value: str) -> bool:
return bool(SCIENTIFIC.match(value))
is_number("42") # True
is_number("-3.14") # True
is_number("1.5e10") # True
is_number("3.14.15") # FalsePHP:
function isNumber(string $value): bool {
return (bool) preg_match('/^-?\d+(\.\d+)?([eE][-+]?\d+)?$/', $value);
}
// Or use the built-in:
is_numeric("42"); // true
is_numeric("-3.14"); // true
is_numeric("0x1A"); // true (also accepts hex, regex above does not)
is_numeric("1e10"); // true (scientific)In PHP, is_numeric is broader than the regex (it accepts hex and binary in some versions). Use the regex when you want to lock down exactly which numeric forms are acceptable.
Engine compatibility
These patterns use only universal features. They run unmodified everywhere. The per-engine notes are about parse helpers when the regex passes and you want the actual numeric value.
| Engine | Practical pattern | Native parse helper |
|---|---|---|
| JavaScript | Works | Number(s) and parseFloat(s). Number.isFinite(Number(s)) filters non-numbers. |
Python (re) | Works | int(s), float(s), decimal.Decimal(s). Raise ValueError on bad input. |
| PHP (PCRE) | Works | is_numeric($v) is broader than the regex (accepts hex/octal in older versions). |
| Java | Works | Integer.parseInt, Double.parseDouble, BigDecimal.new. |
| .NET | Works | int.TryParse, decimal.TryParse(s, NumberStyles.Currency, ...). |
| Go (RE2) | Works | strconv.Atoi, strconv.ParseFloat. No lookaround so the strict patterns below stay simple. |
Rust (regex crate) | Works | s.parse::<i64>(), s.parse::<f64>(). |
| Ruby | Works | Integer(s) and Float(s) raise on invalid. s.to_i silently accepts garbage. |
POSIX ERE (grep -E) | Works (replace \d with [0-9]) | None; combine with shell arithmetic |
I usually run the regex first to confirm the input is shaped right, then Number(s) (JavaScript) or Decimal(s) (Python) for the actual numeric conversion. Skipping the regex and relying on Number(s) alone is also fine if you don't mind NaN for invalid input.
Common mistakes
The bugs I see most often.
Forgetting the second anchor. ^\d+ matches 42abc because nothing pins the end. For validation, anchor both ends.
Treating \d as Unicode-aware. In most engines, \d matches ASCII 0-9 only by default. In .NET it can match Unicode digits depending on options; in PCRE with (*UCP) mode it does too. If your input might include 0 (full-width digit) or Arabic-Indic numerals, decide whether to accept them and choose the engine flag accordingly.
Not handling the explicit + sign. ^-?\d+$ rejects +42 because + is not in the pattern. Use ^[+-]?\d+$ if you want to accept either explicit sign.
Accepting leading zeros silently. Some applications interpret a leading zero as octal. ^[1-9]\d*$ or ^(0|[1-9]\d*)$ are the safer forms for inputs the application will later parse as an integer.
Locale confusion. US uses , for thousands and . for decimal. Most of Europe uses . for thousands and , for decimal. The pattern needs to match the locale of the input, not the locale of the developer.
Trusting the regex to validate the range. ^\d+$ matches 99999999999999999999999999 (way past 64-bit). If your downstream code is int(s), that throws; if it's Number(s), you get Infinity or a loss-of-precision float. Add a length cap or parse and check.
Test cases
| Input | Integer | Decimal | Scientific |
|---|---|---|---|
42 | Match | Match | Match |
-42 | Match | Match | Match |
+42 | No match | No match | No match |
3.14 | No match | Match | Match |
-3.14 | No match | Match | Match |
.5 | No match | No match | No match |
5. | No match | No match | No match |
1.5e10 | No match | No match | Match |
1,234 | No match | No match | No match |
0123 | Match | Match | Match |
1e | No match | No match | No match |
FAQ
See also
- How to Validate a Credit Card Number with Regex: the 13-19 digit cousin pattern with the Luhn checksum
- How to Match a Hex Color Code with Regex: the hex-digits-only sibling used in CSS and design contexts
- How to Match a Date with Regex: year, month, and day are all numbers with bounded ranges
- Regex Anchors: why
^and$matter for number validation - Regex Word Boundaries: scanning numbers out of mixed text without full-string anchors
- Regex Lookaheads and Lookbehinds: adding constraints like "must be followed by a unit"
- Regex Capturing Groups and Backreferences: pull the integer and fractional parts out separately
- Validate Password Strength with Regex: the required-digit lookahead is the most common production use of a number-matching pattern
- Regex Cheat Sheet: the wider syntax and engine compatibility reference
External reference: paste the patterns into regex101.com to test against your own inputs.





