Skip to content

[treeplayer] Evaluate long through long double in Scan and report values that can't be printed exactly#22620

Open
guitargeek wants to merge 2 commits into
root-project:masterfrom
guitargeek:issue-7844
Open

[treeplayer] Evaluate long through long double in Scan and report values that can't be printed exactly#22620
guitargeek wants to merge 2 commits into
root-project:masterfrom
guitargeek:issue-7844

Conversation

@guitargeek

Copy link
Copy Markdown
Contributor

TTree::Scan could silently round 64-bit integer columns, because TTreeFormula evaluates them through a floating-point accumulator. This PR improves that in two ways, one per commit:

  1. Evaluate the "long" (ld/lu) format through long double, like the "long long" format already does, so the full 64-bit value prints exactly wherever long double is wide enough (e.g. x86-64), without changing TTreeFormula's floating-point arithmetic.
  2. Emit a loud error for values that still can't be represented exactly (narrow long double, or results overflowing 2⁶⁴), turning a silent rounding into a clear known-limitation diagnostic.

See the individual commit messages for the full rationale, in particular why this is not fixed by evaluating over integers (it would change long-standing arithmetic semantics).

Closes #7844.

🤖 Done with the help of Claude Code (Claude Opus 4.8), although the problem and my requirements were so precisely specified that a less capable model could probably have reached the same result. The wording of inline comments and error messages was the only remaining degree of freedom.

TTree::Scan prints each column with TTreeFormula::PrintValue, which
evaluates it in a floating-point accumulator. The "long" integer format
("ld"/"lu") used a double, whose 53-bit mantissa rounds any 64-bit
integer above 2^53, whereas the "long long" format ("lld"/"llu") already
used a long double. Promote "ld"/"lu" to long double as well, so that on
platforms where long double is wide enough -- e.g. x86-64, with a 64-bit
mantissa -- the full 64-bit value prints exactly, consistently with the
"long long" format.

This deliberately does NOT switch the evaluation to a 64-bit *integer*
accumulator, even though that would represent every value exactly.
TTreeFormula evaluates every column through a single numeric accumulator
whose type defines the arithmetic, and that arithmetic has always been
floating point. Carrying integers instead would silently change
long-standing, effectively frozen semantics:

  - "x/2" would become integer division instead of floating division;
  - literal constants would be truncated ("x*0.5" -> "x*0");
  - division and modulo of unsigned values above 2^63 would differ,
    because the evaluation is signed.

No accumulator type is simultaneously exact for every 64-bit integer and
faithful to the existing floating-point arithmetic; the two goals are
mutually exclusive for exactly the values at stake. Widening the float
keeps the arithmetic bit-for-bit identical while extending the range of
integers that are printed exactly.

🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Claude Opus 4.8)
@guitargeek guitargeek self-assigned this Jun 15, 2026
@guitargeek guitargeek changed the title [treeplayer] Evaluate the "long" Scan format through long double and report integer Scan values that still cannot be printed exactly [treeplayer] Evaluate long Scan format through long double and report integer values that still can' be printed exactly Jun 15, 2026
@guitargeek guitargeek changed the title [treeplayer] Evaluate long Scan format through long double and report integer values that still can' be printed exactly [treeplayer] Evaluate long Scan format through long double and report values that still can't be printed exactly Jun 15, 2026
@guitargeek guitargeek changed the title [treeplayer] Evaluate long Scan format through long double and report values that still can't be printed exactly [treeplayer] Evaluate long values through long double in Scan and report values that can't be printed exactly Jun 15, 2026
@guitargeek guitargeek changed the title [treeplayer] Evaluate long values through long double in Scan and report values that can't be printed exactly [treeplayer] Evaluate long through long double in Scan and report values that can't be printed exactly Jun 15, 2026
Even evaluated through long double, an integer column can still exceed
the accumulator's exactly-representable range: above 2^53 where long
double is merely a 64-bit double (e.g. macOS ARM), or beyond 2^64 for
results that overflow. As explained in the preceding commit, this cannot
be fixed without changing TTreeFormula's frozen floating-point
arithmetic, so it is a genuine known limitation, not a bug to work
around -- but it must not pass silently.

TTreeFormula::PrintValue now emits an error whenever the value it is
about to print as an integer has reached the point where the long double
accumulator can no longer hold every integer exactly, so the printed
digits may be rounded. The diagnostic is deliberately a kError, issued
for every offending value with no deduplication: silently printing a
wrong integer is precisely the trap we want to surface as loudly as
possible, even on a many-row Scan.

The threshold check is inclusive (>= 2^digits) on purpose: a rounded
result can land back exactly on the threshold (e.g. 2^53 + 1 -> 2^53 in
a 53-bit type), and an inclusive comparison avoids missing those.

Closes root-project#7844.

🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Claude Opus 4.8)
Comment on lines +5116 to +5117
LongDouble_t v = ((TTreeFormula *)this)->EvalInstance<LongDouble_t>(instance);
CheckIntegerPrintPrecision(v, GetTitle());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider

Suggested change
LongDouble_t v = ((TTreeFormula *)this)->EvalInstance<LongDouble_t>(instance);
CheckIntegerPrintPrecision(v, GetTitle());
if (this->IsInteger()) {
Long64_t vi = ((TTreeFormula *)this)->EvalInstance<Long64_t>(instance);
snprintf(value, kMAXLENGTH, Form("%%%s", decform), vi);
} else {
LongDouble_t v = ((TTreeFormula *)this)->EvalInstance<LongDouble_t>(instance);
CheckIntegerPrintPrecision(v, GetTitle());
snprintf(value, kMAXLENGTH, Form("%%%s", decform), (Long64_t)v);
}

except we probably can not tell the difference between signed and unsigned :(

@github-actions

Copy link
Copy Markdown

Test Results

    21 files      21 suites   3d 14h 11m 8s ⏱️
 3 867 tests  3 792 ✅   0 💤 75 ❌
73 538 runs  73 306 ✅ 157 💤 75 ❌

For more details on these failures, see this check.

Results for commit 27cbd7b.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TTree::Scan() (and TTree::Draw()) issues with ULong64_t

2 participants