Idea: lossless number round-tripping (raw / arbitrary-precision number mode)
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 50.6k
- Forks
- 7.5k
- Avg merge
- 4d 17h
- Merged PRs (30d)
- 58
Description
Status: brainstorming, not a plan
This is an idea to move forward on, not a sanctioned TODO or a commitment. It is a hypothesis to investigate, measure, and discuss before anything is decided. Treat this as a starting point for discussion.
Motivation
The lexer scans a number token and immediately converts it to number_integer_t / number_unsigned_t / number_float_t; the original lexeme is then discarded. Consequences:
- A 30-digit integer overflows
uint64_tand silently degrades todouble— precision gone. 1e999becomesinf(or a parse error, depending on path) and is unrecoverable.1.10,100.0,1.0e2,1E2all collapse to the samedoubleand re-emit as1.1/100.0, so a document cannot be round-tripped byte-for-byte. This breaks signing, diffing, and "don't touch fields I didn't edit" workflows.
Note: the integer-vs-float distinction is already preserved (separate value_ts). This idea is specifically about magnitude and textual fidelity.
Prior art
serde_jsonarbitrary_precisionfeature — stores the original number string, parses lazily.- Boost.JSON parse options /
number_precision. - Python
jsonparse_float/parse_inthooks (e.g.Decimal).
Sketch — a "raw number" value
Keep the token. The union already stores a string_t* (include/nlohmann/json.hpp:463), so the cheapest encoding is a new value_t::number_raw whose payload reuses the string pointer to hold the original lexeme:
- parser (raw mode on): don't convert — store the token verbatim.
dump(): emit the token byte-for-byte → perfect round-trip.get<double>()/get<int64_t>(): parse on demand from the stored token.get<json::number_string>()(new): hand back the raw lexeme.
Make it a compile-time / parse-time policy, not a new default
A value_t::number_raw that every consumer must now handle would be an API/ABI break and adds a branch to every numeric access. Gate it behind a parse option / policy, e.g.:
auto j = json::parse(s, /*cb=*/nullptr, /*allow_exceptions=*/true,
/*ignore_comments=*/false,
json::number_policy::raw); // illustrative
so code that doesn't ask for it keeps seeing exactly the current three numeric types.
The hard part: comparison and hashing
This lands directly on top of existing numeric-correctness work — #5256 (std::hash contract), #5210 / #5211 (mixed number_unsigned vs number_integer comparison). Once number_raw exists we must define:
- Does raw
1e3equal integer1000? Equaldouble 1000.0? operator==almost certainly wants numeric equality (parse-and-compare), which then forcesstd::hashto hash the numeric value, not the string — otherwise we re-introduce the exact "equal but different hash" bug.
Recommendation: settle the general numeric comparison/hash model first (a canonical numeric key across integer/unsigned/float), then slot number_raw into it, rather than building fidelity on a cracked foundation.
Open questions
- Storage: reuse the string pointer + a new
value_t, or a dedicatedNumberRawTypetemplate slot? - Lazy-parse caching: parse once and memoize, or re-parse per access?
- What does
type()/is_number()/is_number_integer()report for a raw value before it's been inspected? - Behavior on
dump()after the value has been assigned a normal numeric type (fidelity only survives untouched values).
Dependencies / interactions
- Shares "keep the token, parse lazily" with the borrowed-view idea (build together).
- Deliberately conflicts with the canonical-serialization (JCS) idea: canonical mode must re-canonicalize raw numbers (parse → ECMAScript format), not echo the token. That override needs to be explicit in both designs.
- Gated by resolving the numeric comparison/hash model (#5256, #5210/#5211).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading include/nlohmann/json.hpp around line 463 and the parser, value representation, dump(), and get() entry points. Review the numeric comparison and hashing work in #5256, #5210, and #5211, then resolve the policy, storage, type behavior, and comparison questions; done means an agreed design with precision and textual round-trip tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- backend-api-design, data
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100