Skip to content

fix: accept subnormal and underflowing doubles when parsing (#1427)#1695

Merged
baylesj merged 2 commits into
masterfrom
fix/1427-subnormal-roundtrip
Jun 16, 2026
Merged

fix: accept subnormal and underflowing doubles when parsing (#1427)#1695
baylesj merged 2 commits into
masterfrom
fix/1427-subnormal-roundtrip

Conversation

@baylesj

@baylesj baylesj commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Fixes #1427.

Problem

decodeDouble parses numbers with istringstream >> double. For a subnormal value such as 3.2114e-312, operator>> sets failbit (the result underflowed) even though it produced the correctly-rounded value. The failure path only handled overflow, so subnormals fell through to "… is not a number." — meaning a value jsoncpp just serialized could fail to parse back:

Json::Value(3.2114e-312)  ->  "3.2113999999995192e-312"  ->  parse FAILS

Fix

In the operator>> failure path, accept the value when it is a subnormal (std::fpclassify(value) == FP_SUBNORMAL). This keys off the value operator>> produces — which is the correctly-rounded subnormal on libstdc++, libc++, and MSVC — so it needs no errno/eof heuristics (both of which proved non-portable: MSVC doesn't reliably set errno == ERANGE, and eof() also accepts malformed-but-complete tokens).

It deliberately does not accept results that round to zero, so malformed numbers like 0e / 0e+ (jsonchecker fail29/fail30) and other junk remain rejected. Applied to both Reader and OurReader. Locale-independence from #1662 is preserved.

Tests

New CharReaderTest/parseSubnormal covers subnormals (3.2114e-312, -1e-320, smallest subnormal 4.9e-324), a writer round-trip, and continued rejection of 1abc / 0e / 0e+. Verified locally against the JSON conformance suite (all 96 pass) and across the full CI matrix incl. MSVC. Targets the 1.10.0 line.

@coveralls

coveralls commented Jun 16, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 27653693840

Coverage remained the same at 89.959%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: 1 uncovered change across 1 file (1 of 2 lines covered, 50.0%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
src/lib_json/json_reader.cpp 2 1 50.0%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 2732
Covered Lines: 2608
Line Coverage: 95.46%
Relevant Branches: 2626
Covered Branches: 2212
Branch Coverage: 84.23%
Branches in Coverage %: Yes
Coverage Strength: 23813.72 hits per line

💛 - Coveralls

@baylesj baylesj force-pushed the fix/1427-subnormal-roundtrip branch from 04e7d25 to d6a5e5d Compare June 16, 2026 22:33
decodeDouble parsed numbers via `istringstream >> double`. For a
subnormal value such as `3.2114e-312`, operator>> sets failbit (the
result underflowed) even though it produced the correctly-rounded value.
The failure path only special-cased overflow, so subnormals were
rejected as "not a number" -- meaning a value jsoncpp had just serialized
could fail to parse back.

In the failure path, accept the value when it is a subnormal
(std::fpclassify(value) == FP_SUBNORMAL). This keys off the value
operator>> produces, which is the correctly-rounded subnormal on
libstdc++, libc++, and MSVC, so it needs no errno/eof heuristics. It
deliberately does not accept results that round to zero, so malformed
numbers like "0e" / "0e+" (jsonchecker fail29/fail30) and other junk are
still rejected. Applied to both Reader and OurReader.

Adds CharReaderTest/parseSubnormal covering subnormals, a writer
round-trip, and continued rejection of malformed numbers.
@baylesj baylesj force-pushed the fix/1427-subnormal-roundtrip branch from d6a5e5d to 30ac06a Compare June 16, 2026 22:47
@baylesj baylesj merged commit 1127961 into master Jun 16, 2026
48 checks passed
@baylesj baylesj deleted the fix/1427-subnormal-roundtrip branch June 16, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Roundtrip with subnormal float fails to parse

2 participants