Skip to content

Commit e0e9ecb

Browse files
committed
[treeplayer] Evaluate the "long" Scan format through long double
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)
1 parent a622764 commit e0e9ecb

2 files changed

Lines changed: 12 additions & 8 deletions

File tree

tree/treeplayer/src/TTreeFormula.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5072,7 +5072,7 @@ char *TTreeFormula::PrintValue(Int_t mode, Int_t instance, const char *decform)
50725072
{
50735073
switch (outputSizeLevel) {
50745074
case 0: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(Short_t)((TTreeFormula*)this)->EvalInstance(instance)); break;
5075-
case 2: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(Long_t)((TTreeFormula*)this)->EvalInstance(instance)); break;
5075+
case 2: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(Long_t)((TTreeFormula*)this)->EvalInstance<LongDouble_t>(instance)); break;
50765076
case 3: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(Long64_t)((TTreeFormula*)this)->EvalInstance<LongDouble_t>(instance)); break;
50775077
case 1:
50785078
default: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(Int_t)((TTreeFormula*)this)->EvalInstance(instance)); break;
@@ -5086,7 +5086,7 @@ char *TTreeFormula::PrintValue(Int_t mode, Int_t instance, const char *decform)
50865086
{
50875087
switch (outputSizeLevel) {
50885088
case 0: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(UShort_t)((TTreeFormula*)this)->EvalInstance(instance)); break;
5089-
case 2: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(ULong_t)((TTreeFormula*)this)->EvalInstance(instance)); break;
5089+
case 2: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(ULong_t)((TTreeFormula*)this)->EvalInstance<LongDouble_t>(instance)); break;
50905090
case 3: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(ULong64_t)((TTreeFormula*)this)->EvalInstance<LongDouble_t>(instance)); break;
50915091
case 1:
50925092
default: snprintf(value,kMAXLENGTH,Form("%%%s",decform),(UInt_t)((TTreeFormula*)this)->EvalInstance(instance)); break;

tree/treeplayer/test/regressions.cxx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -652,12 +652,16 @@ TEST(TTreeScan, ULong64Precision)
652652
************************************************************************************
653653
)Scan"};
654654

655-
// The "long long" column format must print the exact 64-bit value, as well as
656-
// the exact result of integer arithmetic with large constants. The column size
657-
// can either be given separately via "colsize=" (so the format passed to
658-
// TTreeFormula::PrintValue is just "lld") or be embedded in the format token
659-
// itself ("21lld"). Only the former triggered the off-by-one in the
660-
// length-modifier detection, but both must yield the exact output.
655+
// Each format is checked both with the column size given separately via
656+
// "colsize=" (so the format reaching TTreeFormula::PrintValue is just "lld") and
657+
// embedded in the token itself ("21lld"). Only the former triggered the
658+
// off-by-one in the length-modifier detection, but both must behave identically.
659+
// The "ld" ("long") and "lld" ("long long") formats must behave identically too,
660+
// as both evaluate through `long double`.
661+
// long double holds the 61-bit value exactly: every spelling must print the
662+
// exact 64-bit value and the exact result of arithmetic with large constants.
661663
EXPECT_EQ(scanToString("colsize=21 col=lld:lld:lld"), expectedScanOut);
662664
EXPECT_EQ(scanToString("col=21lld:21lld:21lld"), expectedScanOut);
665+
EXPECT_EQ(scanToString("colsize=21 col=ld:ld:ld"), expectedScanOut);
666+
EXPECT_EQ(scanToString("col=21ld:21ld:21ld"), expectedScanOut);
663667
}

0 commit comments

Comments
 (0)