Summary
BigVal's documented contract is "always a JSON-valid numeric literal" in decimal form. Two classes of input break it:
- Binary exponents. The comment above the
big.ParseFloat(s, 10, 0, …) call claims it "accepts decimal and e/E exponent forms only". It does not: math/big also accepts binary exponents p/P in any base. So NewBigVal accepts "1p4", "2.5p-2", "5.p3" and stores them verbatim — these are not decimal literals at all (1p4 = 1×2⁴ = 16; 2.5p-2 = 0.625). canonicalDecimal splits the mantissa only on e/E, so affix normalization also misfires ("5.p3" keeps its trailing dot). A decimal consumer that stops at p reads 2.5p-2 as 2.5 — a 4× silent value corruption; a consumer splicing the string into JSON or generated code produces invalid output.
- Leading zeros. JSON forbids them, but
NewBigVal accepts "05", "007", "-012", "00.5", "05e2", storing canonical forms for which json.Valid is false. canonicalDecimal handles exactly three affixes (leading +, leading dot, trailing dot) and misses this fourth JSON-invalid class.
Reproduction
NewBigVal("1p4") → accepted, stored "1p4" (actual value 16), json.Valid=false
NewBigVal("2.5p-2") → accepted, stored "2.5p-2" (actual value 0.625)
NewBigVal("5.p3") → accepted, stored "5.p3" (trailing dot not normalized)
NewBigVal("05") → accepted, stored "05", json.Valid=false
NewBigVal("+05") → accepted, stored "05", json.Valid=false
(Verified twice, independently.) Other garbage is correctly rejected: " 1", "1_000", "0x10", "--5", "NaN", "Inf", huge exponents.
Root cause
ir/bigval.go:33 (the parse call accepts more than the comment claims) and :49-68 (canonicalDecimal never strips leading zeros, never rejects p forms).
Expected
NewBigVal rejects any input that isn't a decimal/e-exponent literal (a pre-parse syntax check, or reject on the presence of p/P), and canonicalization strips leading zeros so the stored form always satisfies json.Valid. A property test asserting json.Valid([]byte(v.String())) over accepted inputs would pin the whole contract.
Summary
BigVal's documented contract is "always a JSON-valid numeric literal" in decimal form. Two classes of input break it:big.ParseFloat(s, 10, 0, …)call claims it "accepts decimal and e/E exponent forms only". It does not:math/bigalso accepts binary exponentsp/Pin any base. SoNewBigValaccepts"1p4","2.5p-2","5.p3"and stores them verbatim — these are not decimal literals at all (1p4= 1×2⁴ = 16;2.5p-2= 0.625).canonicalDecimalsplits the mantissa only one/E, so affix normalization also misfires ("5.p3"keeps its trailing dot). A decimal consumer that stops atpreads2.5p-2as2.5— a 4× silent value corruption; a consumer splicing the string into JSON or generated code produces invalid output.NewBigValaccepts"05","007","-012","00.5","05e2", storing canonical forms for whichjson.Validis false.canonicalDecimalhandles exactly three affixes (leading+, leading dot, trailing dot) and misses this fourth JSON-invalid class.Reproduction
(Verified twice, independently.) Other garbage is correctly rejected:
" 1","1_000","0x10","--5","NaN","Inf", huge exponents.Root cause
ir/bigval.go:33(the parse call accepts more than the comment claims) and:49-68(canonicalDecimalnever strips leading zeros, never rejectspforms).Expected
NewBigValrejects any input that isn't a decimal/e-exponent literal (a pre-parse syntax check, or reject on the presence ofp/P), and canonicalization strips leading zeros so the stored form always satisfiesjson.Valid. A property test assertingjson.Valid([]byte(v.String()))over accepted inputs would pin the whole contract.