Repository navigation
pxf: refuse a BigFloat literal below the wire's exponent range - #142
Merged
Merged
Conversation
trendvidia
force-pushed
the
pxf-bigfloat-below-range-125
branch
from
November 4, 2026 19:17
e1d6319 to
c561e02
Compare
Collaborator
Author
trendvidia
force-pushed
the
pxf-bigfloat-below-range-125
branch
from
November 4, 2026 22:52
c561e02 to
81db6a1
Compare
Collaborator
Author
The wire carries a BigFloat's exponent less its prec as an int32 (pxf/bignum.proto). A small value at a wide prec, such as 0.125p2147483647, has a binary exponent of -2 and a wire exponent of -2 - (2^31 - 1), below INT32_MIN, which SetBigFloatFromLiteral wrapped. protowire-go refuses it as "big float literal <s> is below the wire's exponent range" (protowire-go#231), and now so does this port. The check needs the exponent of the value rounded to prec, which the parser had only after widening the value to prec bits: 256 MiB at the prec that reaches this, and 9.7 s and 1.1 GB in a debug test. So both exponent checks now run first, on an exponent found from the literal's bit lengths and one comparison. Rounding carries it one higher exactly when the value is at least 2^e (1 - 2^-(prec+1)), which is a comparison on numbers no wider than the literal's own, since past that width the gap below 2^e is too large to carry. The division afterwards asserts it found the same exponent. The below-range check comes before the bound, as in Go. The test of it costs 0.1 s; a differential of 1606 literals built around rounding ties, whose expected exponents come from their construction, pins the arithmetic.
trendvidia
force-pushed
the
pxf-bigfloat-below-range-125
branch
from
November 5, 2026 04:19
81db6a1 to
f17f0fc
Compare
Collaborator
Author
|
Rebased onto main 9b9860a: 81db6a1 → f17f0fc. The earlier red "Differential against protowire-go" was |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #125.
A
BigFloatliteral like0.125p2147483647can't be encoded. The wire stores the exponent minus prec as anint32(pxf/bignum.proto). Here that is -2 - (2^31 - 1), belowINT32_MIN. This port accepted the literal and itsstatic_cast<int32_t>wrapped the exponent. The wrap is by reading the code; the acceptance is measured below. protowire-go refuses it (protowire-go#231), and this port now does too, in Go's words:Why the restructure
The check needs the exponent of the value after rounding to prec. The parser only had that after widening the value to prec bits, which is 256 MiB at the prec that reaches this case. The issue's open question was whether to accept a heavy test or restructure. The done-when asks for a test whose cost suits CI, so this restructures.
Both exponent checks now run before the widening. They use an exponent worked out from the literal's numerator and denominator (
RoundedBinaryExponentinsrc/pxf/bigfloat.cc):g * 2^(p+1) <= A. Since g >= 1, it can't hold once p + 1 reaches A's bit length. So the shift is never wider than A, and A's size is bounded by the literal. A wide prec costs nothing here.After the division, the parser checks it got the same exponent, and fails loudly if not. The "below range" check runs before the exponent bound, the order in which Go asks them.
Evidence
PxfBigNum.BigFloatBelowTheWiresExponentRangeIsRefusedtakes 0.10 s. It checks four literals and that nothing gets stored. With the check removed, the same test fails: the literal is accepted, and the run takes 9.65 s and 1.09 GB peak memory in a Debug build.PxfBigNum.BigFloatRoundedExponentIsFoundBeforeDividingreads 1606 literals built around rounding ties (exactly at the tie, just below, just above, and powers of two). Their expected exponents come from how they are built, not from the code under test. Each one also probesmax_big_float_exponentat its exponent, where the early arithmetic decides. With the carry removed fromRoundedBinaryExponent, it fails:internal error: rounded binary exponent -172, expected -173.check_decode --format pxf-pb --limit MaxNumericLiteralDigits=2000000000, protowire-go at 207fdfc:0.125p2147483647: both refuse with the line above.0.25p2147483647(wire exponent exactlyINT32_MIN): both accept and write identical bytes, 268,435,485 of them, exponent80808080f8ffffffff01. That's 10 s here and 1 s in Go, so it was measured once and is not in CI.ctestpasses 658 of 658 in a fresh build dir. clang-format 18 applied.Not changed here
This is separate from #139 (#128, the rounding check on the write side). The two touch different functions in
bigfloat.ccand are not stacked.For a literal far past the bound, such as
1e-100000p64, an older fast check still refuses with this port's own text,is past MaxBigFloatExponent=16384. Go sayshas binary exponent -332192, and MaxBigFloatExponent=16384 bounds its magnitude. That check exists to avoid building a huge power of ten, so matching Go's exact exponent there needs its own design. Measured; I'll file it separately.