Skip to content
Strict Number Grammar
step 1/5

Reading — step 1 of 5

Read

~3 min readParsing Values

Strict Number Grammar

This is the exact number grammar from RFC 8259 — small enough to memorize, strict enough that most languages' built-in float parsers get it wrong:

number = [ "-" ] int [ frac ] [ exp ]
int    = "0" | digit1-9 *digit
frac   = "." 1*digit
exp    = ("e" | "E") [ "+" | "-" ] 1*digit

In English: an optional minus (never a plus); an integer part that is either the single digit 0 or a 1–9 digit followed by any digits; an optional fraction that must contain at least one digit after the dot; an optional exponent that must contain at least one digit, with an optional sign.

Why ban leading zeros? In C and old JavaScript, 010 meant octal 8. JSON removes the ambiguity by making 01 flatly illegal — the only literal starting with 0 is 0 itself (optionally followed by .digits or an exponent). Note -0 is valid, and the grammar puts no ceiling on size: 1e999999 is grammatically perfect JSON even though it overflows every double (RFC 8259 only promises good interop for numbers within IEEE-754 double range).

The canonical rejects — make each one a test in your head:

InputWhy it fails
+5leading plus not in the grammar
01, 00leading zero
.5integer part required
5.frac requires 1+ digits after .
5eexp requires 1+ digits
--5one minus, in one place
0x1Fno hex
NaN, Infinitynot in the grammar at all

The idiom: translate the ABNF into a regex

The grammar is regular (no recursion), so a regex is the honest transcription:

python

Each group is one production: (0|[1-9][0-9]*) is int, (\.[0-9]+)? is [frac], ([eE][+-]?[0-9]+)? is [exp].

Trap 1 — re.match instead of re.fullmatch. match anchors only the start, so 1.5x and 5..2 sail through on their valid prefixes. Anchor both ends.

Trap 2 — using float() as the validator. Python's float() happily accepts +5, 01, Infinity, nan, 1_000 (underscores!), and surrounding whitespace. It answers "can Python make a float from this?", which is a much looser question than "is this an RFC 8259 number?". The exercise exists precisely because those two questions differ.

This strictness is the point of JSON: a lenient parser accepts documents that every other conforming implementation rejects, and the bug surfaces later, in someone else's service.

Your exercise

Validate one candidate per line: print OK or an error. The grader-caught mistake is the error text: every invalid input — 01, +5, .5, 5., 5e, --5, 00, 1., 0x1F, NaN, Infinity — must print exactly the same uniform line ERR invalid number. Do not emit per-case reasons like "leading zero"; whatever diagnosis your code makes internally, the reason string it returns must be invalid number, letter for letter. And confirm the accepts against the hidden tests: -0, 1.0, 1e+10, 1e-10, and -0.5 are all OK.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…