{"addressing":"content-addressed","addressedBy":"d5071a580382120a9e7f72eb998a2958df5c1c50d3629c5775d2e5528635410a","canonicalText":"{\"formatVersion\":1,\"frozen\":{\"address\":{\"language\":\"typescript\",\"major\":1,\"name\":\"number/parse\"},\"benchmarks\":{\"profiles\":[{\"class\":\"accepted\",\"data\":{\"fields\":[],\"kind\":\"record\"},\"description\":\"Short integer strings, the dominant shape in form and query-string input.\",\"name\":\"small-integers\",\"samples\":{\"kind\":\"carried\",\"values\":[{\"kind\":\"primitive\",\"value\":\"0\"},{\"kind\":\"primitive\",\"value\":\"7\"},{\"kind\":\"primitive\",\"value\":\"42\"},{\"kind\":\"primitive\",\"value\":\"1999\"},{\"kind\":\"primitive\",\"value\":\"-15\"}]}},{\"class\":\"accepted\",\"data\":{\"fields\":[],\"kind\":\"record\"},\"description\":\"Fractional and scientific notation, the shape of spreadsheet and CSV exports.\",\"name\":\"decimals-and-exponents\",\"samples\":{\"kind\":\"carried\",\"values\":[{\"kind\":\"primitive\",\"value\":\"3.14159\"},{\"kind\":\"primitive\",\"value\":\"-0.001\"},{\"kind\":\"primitive\",\"value\":\"1e3\"},{\"kind\":\"primitive\",\"value\":\"2.5E-8\"},{\"kind\":\"primitive\",\"value\":\".5\"}]}},{\"class\":\"rejected\",\"data\":{\"fields\":[],\"kind\":\"record\"},\"description\":\"Inputs that must return null. Measured separately because an implementation may take a very different path when it rejects, and validation-heavy callers hit that path most.\",\"name\":\"rejected-inputs\",\"samples\":{\"kind\":\"carried\",\"values\":[{\"kind\":\"primitive\",\"value\":\"\"},{\"kind\":\"primitive\",\"value\":\"   \"},{\"kind\":\"primitive\",\"value\":\"abc\"},{\"kind\":\"primitive\",\"value\":\"0x1F\"},{\"kind\":\"primitive\",\"value\":\"1,5\"},{\"kind\":\"primitive\",\"value\":\"NaN\"}]}},{\"class\":\"accepted\",\"data\":{\"fields\":[],\"kind\":\"record\"},\"description\":\"Valid numbers wrapped in whitespace, isolating the cost of trimming.\",\"name\":\"whitespace-padded\",\"samples\":{\"kind\":\"carried\",\"values\":[{\"kind\":\"primitive\",\"value\":\"  42  \"},{\"kind\":\"primitive\",\"value\":\"\\t7\\n\"},{\"kind\":\"primitive\",\"value\":\"   -3.5   \"}]}},{\"class\":\"accepted\",\"data\":{\"fields\":[],\"kind\":\"record\"},\"description\":\"Pathological lengths, to expose implementations whose cost grows faster than linearly. The thousand digits sit behind the decimal point so that the value stays finite and the sample stays on the accepting path; written as an integer it overflows and measures rejection.\",\"name\":\"long-inputs\",\"samples\":{\"count\":3,\"encodedBytes\":7102,\"kind\":\"produced\",\"producedBy\":\"['0.' + '1'.repeat(1000), '0'.repeat(5000) + '1', ' '.repeat(1000) + '42']\",\"sha256\":\"e7f135c8e728200a0b7bb62b341005fc49069fd75958889f52d86018a39adac4\"}}],\"vocabulary\":[{\"meaning\":\"every sample of the profile is answered rather than refused\",\"name\":\"accepted\"},{\"meaning\":\"every sample of the profile is refused, which is a different path\",\"name\":\"rejected\"}]},\"caseTables\":[{\"cases\":[{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"42\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":42}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"baseline\",\"id\":\"ordinary-integer\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An ordinary integer parses to itself.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"-3.5\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":-3.5}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"baseline\",\"id\":\"ordinary-negative-decimal\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An ordinary negative decimal parses to itself.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"  42  \"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":42}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"whitespace\",\"id\":\"surrounding-whitespace\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Surrounding whitespace is ignored, because text arriving from a form field or a spreadsheet cell routinely carries it. Trimming uses String.prototype.trim.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"\\t\\n 7 \\r\\n\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":7}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"whitespace\",\"id\":\"tabs-and-newlines\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Tabs and newlines are whitespace for String.prototype.trim, so they are ignored too.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"﻿9\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":9}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"whitespace\",\"id\":\"leading-byte-order-mark\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A leading byte-order mark parses, because String.prototype.trim removes U+FEFF: \\\"\\\\uFEFF9\\\".trim() has length 1. This is derived from the whitespace rule rather than a special case, and it matters because text copied out of a document often carries a byte-order mark or a non-breaking space.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"4 2\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"whitespace\",\"id\":\"whitespace-inside-the-number\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Whitespace inside the number is not ignored; only leading and trailing whitespace is.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"+42\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":42}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"sign\",\"id\":\"leading-plus-sign\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A leading plus sign is accepted and has no effect on the value.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"- 1\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"sign\",\"id\":\"sign-detached-from-its-digits\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A sign separated from its digits is not a number.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"--1\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"sign\",\"id\":\"repeated-sign\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A repeated sign is not a number.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"-0\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"number\",\"literal\":\"negative-zero\"}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"sign\",\"id\":\"negative-zero\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Negative zero is preserved, because it is a distinct IEEE-754 value carrying the sign of an underflowing computation. Comparing results with === would hide the difference, since -0 === 0 is true; this contract compares with Object.is.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"01\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":1}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"digit-shapes\",\"id\":\"leading-zeros\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Leading zeros are accepted and carry no meaning. They are not read as octal - that legacy of old parseInt implementations is not part of this contract.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\".5\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":0.5}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"digit-shapes\",\"id\":\"bare-fraction\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A fraction with no integer part is accepted, matching how people write it.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"5.\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":5}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"digit-shapes\",\"id\":\"trailing-decimal-point\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An integer with a trailing decimal point is accepted; it is a common typing artefact.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\".\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"digit-shapes\",\"id\":\"lone-decimal-point\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A decimal point with no digits on either side is not a number.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1.2.3\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"digit-shapes\",\"id\":\"two-decimal-points\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Two decimal points is not a number. parseFloat(\\\"1.2.3\\\") returns 1.2, silently discarding the rest of the input; this contract rejects it instead.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1e3\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":1000}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"exponent-notation\",\"id\":\"exponent\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Scientific notation is accepted, because it is how spreadsheets and scientific exports write large numbers. parseInt(\\\"1e3\\\") returns 1, keeping only the leading digit.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1E+3\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":1000}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"exponent-notation\",\"id\":\"exponent-uppercase-and-signed\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The exponent marker is case-insensitive and its sign is optional.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1e-7\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":1e-7}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"exponent-notation\",\"id\":\"negative-exponent\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A negative exponent is accepted.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1e\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"exponent-notation\",\"id\":\"exponent-with-no-digits\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An exponent marker with no digits is not a number. parseFloat(\\\"1e\\\") returns 1, silently dropping the incomplete exponent.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"empty\"}}],\"kind\":\"record\"},\"group\":\"empty-and-blank\",\"id\":\"the-empty-string\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The empty string is not a number. Number(\\\"\\\") returns 0, the single most damaging trap in JavaScript numeric conversion: an empty form field becomes a legitimate-looking zero. Correcting it is the main reason this contract exists.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"   \"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"empty\"}}],\"kind\":\"record\"},\"group\":\"empty-and-blank\",\"id\":\"a-blank-string\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A blank string is not a number, for the same reason: Number(\\\"   \\\") also returns 0.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"NaN\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"non-finite-words\",\"id\":\"the-word-nan\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The word \\\"NaN\\\" is not a number. Number(\\\"NaN\\\") returns NaN, which is indistinguishable from the failure value of every other invalid input.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"Infinity\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"non-finite-words\",\"id\":\"the-word-infinity\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The word \\\"Infinity\\\" is rejected, because a parser that can return an infinite value forces every caller to guard with isFinite afterwards. Number(\\\"Infinity\\\") returns Infinity and Python accepts float(\\\"Infinity\\\"); this contract knowingly diverges from both. It is not-decimal rather than overflow: the rejection is about the word, not about a magnitude that was computed and lost.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"-Infinity\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"non-finite-words\",\"id\":\"the-word-negative-infinity\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Rejected for the same reason as \\\"Infinity\\\".\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"0x1F\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"alternative-radixes\",\"id\":\"hexadecimal\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Hexadecimal is rejected: this contract reads human decimal text, where \\\"0x1F\\\" is a typo rather than the number 31. Number(\\\"0x1F\\\") returns 31, but JavaScript's own numeric parser disagrees - parseFloat(\\\"0x1F\\\") returns 0 - and Python raises on float(\\\"0x1F\\\").\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"0o17\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"alternative-radixes\",\"id\":\"octal\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Octal notation is rejected, consistently with hexadecimal.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"0b11\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"alternative-radixes\",\"id\":\"binary\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Binary notation is rejected, consistently with hexadecimal.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1,5\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"comma-as-a-decimal-separator\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A comma is never a decimal separator here, and this is the most frequent real refusal in half of Europe: measured, 61 of 108 locales write the decimal point as a comma. The value is refused because reading it as 1.5 or as 15 would both be guesses about the writer, but the reason is not a guess - the text does carry a separator, and a caller can say so.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1,000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"comma-grouping\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A thousands separator, and the exact input that makes guessing indefensible: 1,000 is one thousand in English and one in French. The refusal is the answer; the reason is the repair.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1_000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"underscore-grouping\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The underscore is the one member of the family no locale emits. It is here because it is the digit separator of JavaScript and Python source literals: Number(\\\"1_000\\\") returns NaN while Python's float(\\\"1_000\\\") returns 1000, so a developer who expects the Python answer gets a named reason rather than the residual one.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1,234.56\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"comma-grouping-with-a-decimal-point\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Grouping and a decimal point together, the shape a spreadsheet exports in English. It is listed because it is the case where the separator and the grammar meet: the full stop is a token this grammar reads and the comma is not, so only the comma is removed before the second look.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1'000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"apostrophe-grouping\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The apostrophe groups digits in Swiss and Liechtenstein formatting - measured, de-CH, de-LI, it-CH, gsw, wae and en-CH all emit it. It is the only member of the family that is an ordinary typeable character, and it is included because no measured locale emits it for anything else between two digits.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1 000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"no-break-space-grouping\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A no-break space groups digits in 56 of the 108 locales measured, among them Swedish, Czech, Polish and Russian. It survives a copy out of a document and is invisible to the person who pasted it, which is exactly the population that cannot diagnose a bare refusal.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1 000,5\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"narrow-no-break-space-grouping\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"What a French spreadsheet exports: a narrow no-break space grouping and a comma decimal, both in one string. Measured, fr, fr-BE, fr-CH and rm-CH emit U+202F and not the no-break space, so covering one and not the other would miss the locale the literal exists for.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1,2,3\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"grouping-that-is-not-in-threes\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Grouping that is not in threes is still a separator mistake. This case was cited by an earlier revision of this contract as the reason the literal could not exist - the claim being that classifying it would need a second grammar that knows groups come in threes. The claim was wrong about what the reason has to mean: \\\"this text uses digit separators\\\" is true of 1,2,3 whether or not the grouping is well formed, and no caller acts on the difference.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1.000,5\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"separator\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-covers\",\"id\":\"full-stop-grouping-with-a-comma-decimal\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"German and Italian grouping: the full stop groups and the comma is decimal. Removing the family from this input leaves 1.0005, which is not what the writer meant - and the reason is still correct, because it names the motive and not the repair. A caller that echoes \\\"remove the separators\\\" would be wrong here; one that says \\\"this contract reads 1000.5, not 1.000,5\\\" is right. The literal is a diagnosis, never a rewrite rule.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1 000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"an-ordinary-space-between-digits\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An ordinary space is not formatting. No measured locale emits U+0020 between digits - the ones that group with a space emit U+00A0 or U+202F - so a space here is a typo, and it carries the residual reason for the same purpose that 4 2 does. This is the line that makes the family a decision rather than a habit: it is drawn where the measurement draws it, and a mutant that widens the family to whitespace is caught by this case.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1’000\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"a-typographic-apostrophe-between-digits\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The typographic apostrophe is excluded, and it is the exclusion this contract is least sure of. A word processor turns a typed apostrophe into U+2019, so the character does reach real text; but measured, no locale emits it as a group separator, and the family is drawn from the measurement rather than from what seems likely. Recorded here so that a later revision has the reason it would be overturning.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1٬234\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"the-arabic-thousands-separator\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"The Arabic thousands separator is excluded on a measurement rather than a preference: the locales that emit U+066C emit Arabic-Indic digits with it, and this contract rejects those digits anyway. Adding it to the family would create a branch that no formatted number can reach, which is the shape of a guard that cannot fail.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\",\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"a-separator-with-nothing-to-separate\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A separator with nothing to separate is not a number with separators in it. Removing the family leaves the empty string, which the grammar refuses, so the residual reason is reached - and `empty` is not, because the input was not blank to begin with.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"x,y\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"a-separator-in-text-that-is-not-a-number\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A comma inside something that is not a number at all. It is the other side of the same boundary: the reason is decided by what is left once the family is removed, not by whether a family character appears.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"0x1_F\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"separators-the-family-does-not-cover\",\"id\":\"a-separator-inside-a-radix-prefix\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A separator does not rescue a radix prefix. Removing the underscore leaves 0x1F, which the grammar refuses, so this stays the residual refusal - the same answer it would get without the underscore, which is the point.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"abc\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"not-numbers-at-all\",\"id\":\"arbitrary-text\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Arbitrary text is not a number.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"12n\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"not-numbers-at-all\",\"id\":\"a-bigint-suffix\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A BigInt literal suffix is rejected. parseFloat(\\\"12n\\\") returns 12, silently discarding the suffix that carried the meaning.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"١٢٣\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"not-numbers-at-all\",\"id\":\"arabic-indic-digits\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Digits outside ASCII 0-9 are rejected. Number() also returns NaN for the Arabic-Indic digits U+0661 U+0662 U+0663, even though they spell 123.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"constructor\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"not-decimal\"}}],\"kind\":\"record\"},\"group\":\"not-numbers-at-all\",\"id\":\"an-inherited-property-name\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"An inherited object property name is not a number. It is listed because an implementation memoising into a plain object serves it from Object.prototype: \\\"constructor\\\" in {} is true. Which of the two exports catches that depends on the error convention, and under this one it is no longer the value: measured, parseNumber(\\\"constructor\\\") answers null, which is correct, while describeParseFailure answers undefined where this row requires \\\"not-decimal\\\". Block 4.2 records why that matters to every later contract.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1e400\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":null}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":\"overflow\"}}],\"kind\":\"record\"},\"group\":\"ieee-754-limits\",\"id\":\"overflow-past-the-largest-double\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A value too large for a double is rejected, because Number(\\\"1e400\\\") returns Infinity and the contract guarantees a finite result. The magnitude is lost either way; returning null makes the loss impossible to ignore.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"1e-400\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":0}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"ieee-754-limits\",\"id\":\"underflow-to-zero\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A value too small for a double becomes 0, the nearest representable double under IEEE-754. Unlike overflow this stays finite, so it is accepted - the contract cannot repair IEEE-754, only make its behaviour explicit.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"-1e-400\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"number\",\"literal\":\"negative-zero\"}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"ieee-754-limits\",\"id\":\"negative-underflow\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"A negative underflow becomes -0, keeping the sign of the value that was lost. This is where preserving negative zero pays for itself.\"},{\"data\":{\"fields\":[{\"name\":\"input\",\"value\":{\"kind\":\"primitive\",\"value\":\"9007199254740993\"}},{\"name\":\"expected\",\"value\":{\"kind\":\"primitive\",\"value\":9007199254740992}},{\"name\":\"reason\",\"value\":{\"kind\":\"primitive\",\"value\":null}}],\"kind\":\"record\"},\"group\":\"ieee-754-limits\",\"id\":\"an-integer-past-two-to-the-fifty-third\",\"provenance\":{\"kind\":\"specified\"},\"rationale\":\"Above 2^53 consecutive integers are no longer representable, so this input parses to the nearest double, 9007199254740992. Every JavaScript number parser loses this digit; the contract documents the loss rather than pretending otherwise.\"}],\"groups\":[{\"id\":\"baseline\",\"note\":null,\"title\":\"Baseline\"},{\"id\":\"whitespace\",\"note\":null,\"title\":\"Whitespace\"},{\"id\":\"sign\",\"note\":null,\"title\":\"Sign\"},{\"id\":\"digit-shapes\",\"note\":null,\"title\":\"Digit shapes\"},{\"id\":\"exponent-notation\",\"note\":null,\"title\":\"Exponent\"},{\"id\":\"empty-and-blank\",\"note\":null,\"title\":\"Empty and blank\"},{\"id\":\"non-finite-words\",\"note\":null,\"title\":\"Non-finite words\"},{\"id\":\"alternative-radixes\",\"note\":null,\"title\":\"Alternative radixes\"},{\"id\":\"separators-the-family-covers\",\"note\":\"Every character below was measured rather than chosen: Intl.NumberFormat was asked to format 1234567.5 in 108 locales, and these are the grouping characters it emitted. The value is still refused - this contract is not locale-aware and will not guess which separator was decimal - but the reason names what the writer did, instead of calling their number \\\"not a number\\\".\",\"title\":\"Separators: the characters the family covers\"},{\"id\":\"separators-the-family-does-not-cover\",\"note\":null,\"title\":\"Separators: the characters the family does not cover\"},{\"id\":\"not-numbers-at-all\",\"note\":null,\"title\":\"Not numbers at all\"},{\"id\":\"ieee-754-limits\",\"note\":null,\"title\":\"IEEE-754 limits\"}],\"name\":\"edge-cases\",\"purpose\":\"the inputs this contract settles one at a time, because a grammar has no algebra\"}],\"environments\":[\"node\",\"browser\",\"bun\"],\"harness\":[{\"bytes\":17102,\"path\":\"contract.ts\",\"sha256\":\"e89126c45f6b1dc3b84e0a469b70f72b99cd54212e6fbf56c34517106780c182\"},{\"bytes\":3906,\"path\":\"edge-cases.test.ts\",\"sha256\":\"a21daac8a0b91e007ac87898f5082b34610898d00cabfc7695413d0e3fc0dbfa\"},{\"bytes\":24256,\"path\":\"edge-cases.ts\",\"sha256\":\"595ea9d673b2c332f2de7720c3fdb21c0b8361515f7830ee3f10d8027d6985d3\"},{\"bytes\":2536,\"path\":\"profiles.test.ts\",\"sha256\":\"d47a8fa2b9dfebf13ea0650238efb1ccae688f999670516dcb6f62a969fbd86d\"},{\"bytes\":7030,\"path\":\"properties.test.ts\",\"sha256\":\"5f4ff696c4dd6d0aca92eb92c805796a4e9ba7871bf6349673312e2cbc37aa8a\"},{\"bytes\":4299,\"path\":\"reference.ts\",\"sha256\":\"d1a5f60ef00fa46aaa516193add7077751f17d6e89b8695af8d884473123ddc0\"},{\"bytes\":1242,\"path\":\"signature.test-d.ts\",\"sha256\":\"5e00e6ff9498643374df834d2ae81c12a3cc0f103a155eb24182714bb6e29620\"}],\"identity\":{\"description\":\"Converts a string to a number in JavaScript and TypeScript, returning null instead of a misleading value when the string is not a number. JavaScript offers three built-in ways to convert a string to a number and all three lie in common cases: Number(\\\"\\\") returns 0, Number(\\\" \\\") returns 0, Number(\\\"0x1F\\\") returns 31, parseFloat(\\\"1.2.3\\\") returns 1.2 and parseInt(\\\"1e3\\\") returns 1. Each of them can also return NaN, a value that survives every arithmetic operation and surfaces far away from the mistake that produced it. This contract accepts the decimal grammar a human writes - optional surrounding whitespace, optional sign, leading zeros, a fractional part, scientific notation - and rejects everything else, including hexadecimal, octal, binary, digit separators, \\\"NaN\\\" and \\\"Infinity\\\". The result is either null or a finite number: never NaN, never Infinity. Which of the four refusals happened is published by a second export rather than folded into the return value, so a caller who only needs a number is not made to unwrap one. One of the four is that the text carries digit separators - \\\"1,5\\\", \\\"1 000\\\", \\\"1'000\\\" - which is what a French, German, Swiss or Nordic spreadsheet exports, and a caller holding that reason can correct its user instead of telling them their number is not a number.\",\"exportName\":\"parseNumber\",\"inputDomain\":\"Human-authored decimal text: form fields, CSV and spreadsheet cells, CLI arguments, environment variables, query-string parameters, and text pasted out of documents. It is not a reader for JavaScript source literals, not a locale-aware parser, and not an arbitrary precision parser.\",\"searchAliases\":[\"string to number\",\"convert string to number\",\"to number\",\"toNumber\",\"parse float\",\"parseFloat\",\"parse int\",\"parseInt\",\"cast to number\",\"string to int\",\"string to float\",\"number from string\",\"safe parse number\"],\"summary\":\"Convert a string to a finite number, or null when the string is not a decimal number.\"},\"ownDeclarations\":[],\"properties\":{\"runs\":1000,\"universal\":[{\"applicable\":false,\"name\":\"never mutates its arguments\",\"reason\":\"The signature takes a single `string`, a primitive that is immutable by construction in JavaScript. No implementation, correct or broken, can violate this, so a test asserting it would be structurally incapable of failing.\"},{\"applicable\":true,\"name\":\"deterministic\",\"reason\":\"Violable in practice, and witnessed by P-08 of the battery: a regular expression carrying the global flag keeps a lastIndex between calls and answers differently on the second one. This property is ordered under `no ambient input` rather than independent of it: every mutant measured to redden it reddens that one too, and the memoise-last mutant reddens that one and not this. P-21 is that mutant here.\"},{\"applicable\":true,\"name\":\"no ambient input\",\"reason\":\"Violable in practice: a cache keyed on the wrong thing, or a regular expression whose lastIndex survives a call, makes an answer depend on which inputs were parsed before it. The property interleaves a probe with an arbitrary history and requires the probe to answer identically either way. This contract reads a string and returns a number, so the call history is the only ambient input it can plausibly acquire. Witnessed alone by P-21, which remembers its last analysis under the length of the input: measured, ten guards redden and determinism is not one of them. The caches that are consulted first and never advance - P-02, P-17, P-19 - stay invisible to it, because the probe primes them itself.\"},{\"applicable\":false,\"name\":\"no ambient output\",\"reason\":\"Not reachable by a property - a test cannot observe a write that happened before it ran, and a correct memoising cache is indistinguishable from a defect by behaviour alone. Measured here first, on the implementation that writes globalThis.__parseNumberCalls and passes the whole suite, and the attempt was removed rather than left decorative.\"}]},\"sharedHarness\":[{\"bytes\":16621,\"path\":\"packages/catalogue/every-contract.ts\",\"sha256\":\"651fcc16fdc900bcded8e572bc5bb044c52322bb9c04bc2c05630e2761d5cc16\"},{\"bytes\":9777,\"path\":\"packages/catalogue/identifier.ts\",\"sha256\":\"8ab32ffcd699e39aa4fcb7604722f28dc12b723fca3a126a413c3e0a26ff3ff6\"}],\"surface\":{\"couplingRule\":\"parseNumber(s) === null if and only if describeParseFailure(s) !== null, for every string s\",\"exports\":[{\"name\":\"parseNumber\",\"parameters\":[{\"name\":\"input\",\"type\":\"string\"}],\"role\":\"the-answer\",\"text\":\"(input: string) => number | null\",\"typeName\":\"ParseNumber\"},{\"name\":\"describeParseFailure\",\"parameters\":[{\"name\":\"input\",\"type\":\"string\"}],\"role\":\"the-diagnostic\",\"text\":\"(input: string) => ParseFailureReason | null\",\"typeName\":\"DescribeParseFailure\"}],\"failureReasons\":[\"empty\",\"separator\",\"not-decimal\",\"overflow\"],\"supportingTypes\":[]}},\"unit\":\"contract\"}","formatVersion":1}