Skip to content

Commit 803c3db

Browse files
committed
[treeplayer] Fix precision loss in TTree::Scan for 64-bit integers
The `"lld"` column format passed without an embedded size (e.g. via `"colsize=N col=lld"`) was not recognized as a `"long long"` modifier in **TTreeFormula::PrintValue**, so 64-bit integer branches (e.g. `ULong64_t`) were evaluated as double and rounded above 2^53. This commit fixes a off-by-one problem in the length-modifier detection to address the issue, adding also a regression test. Closes #7844. 🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Claude Opus 4.8)
1 parent 06bd32f commit 803c3db

2 files changed

Lines changed: 67 additions & 2 deletions

File tree

tree/treeplayer/src/TTreeFormula.cxx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5052,12 +5052,12 @@ char *TTreeFormula::PrintValue(Int_t mode, Int_t instance, const char *decform)
50525052
Ssiz_t len = strlen(decform);
50535053
Char_t outputSizeLevel = 1;
50545054
char *expo = nullptr;
5055-
if (len>2) {
5055+
if (len > 1) {
50565056
switch (decform[len-2]) {
50575057
case 'l':
50585058
case 'L': {
50595059
outputSizeLevel = 2;
5060-
if (len>3 && tolower(decform[len-3])=='l') {
5060+
if (len > 2 && tolower(decform[len - 3]) == 'l') {
50615061
outputSizeLevel = 3;
50625062
}
50635063
break;

tree/treeplayer/test/regressions.cxx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,3 +585,68 @@ TEST(TTreeScan, TTreeGetBranchOfFriendTChain)
585585
throw std::runtime_error("Could not retrieve TTreePlayer from main tree!");
586586
}
587587
}
588+
589+
// https://github.com/root-project/root/issues/7844
590+
// TTree::Scan() used to lose precision when printing 64-bit integer branches
591+
// (e.g. ULong64_t): the "lld" column format, when given without an embedded
592+
// column size (i.e. via "colsize=N col=lld"), was not recognized as a
593+
// "long long" modifier because of an off-by-one in the length-modifier
594+
// detection in TTreeFormula::PrintValue. As a consequence the value was
595+
// evaluated and printed as a double, rounding anything above 2^53.
596+
TEST(TTreeScan, ULong64Precision)
597+
{
598+
// 1617047019150033926 needs 61 bits, so it cannot be represented exactly
599+
// by a double (53-bit mantissa).
600+
constexpr ULong64_t value{1617047019150033926ULL};
601+
602+
struct DatasetRAII {
603+
const char *fTreeName{"tree_7844"};
604+
const char *fFileName{"tree_7844.root"};
605+
DatasetRAII(ULong64_t v)
606+
{
607+
auto file = std::make_unique<TFile>(fFileName, "recreate");
608+
auto tree = std::make_unique<TTree>(fTreeName, fTreeName);
609+
610+
ULong64_t x = v;
611+
tree->Branch("x", &x, "x/l");
612+
tree->Fill();
613+
file->Write();
614+
}
615+
616+
~DatasetRAII() { std::remove(fFileName); }
617+
} dataset{value};
618+
619+
auto file = std::make_unique<TFile>(dataset.fFileName);
620+
std::unique_ptr<TTree> tree{file->Get<TTree>(dataset.fTreeName)};
621+
622+
std::ostringstream strCout;
623+
{
624+
if (auto *treePlayer = static_cast<TTreePlayer *>(tree->GetPlayer())) {
625+
struct FileRAII {
626+
const char *fPath;
627+
FileRAII(const char *name) : fPath(name) {}
628+
~FileRAII() { std::remove(fPath); }
629+
} redirectFile{"tree_7844_regression_redirect.txt"};
630+
treePlayer->SetScanRedirect(true);
631+
treePlayer->SetScanFileName(redirectFile.fPath);
632+
// The "lld" format without an embedded size (the size is given
633+
// separately via "colsize=") must print the exact 64-bit value, as
634+
// well as the exact result of integer arithmetic with large constants.
635+
tree->Scan("x:x-1617047019150033925:x-1617047019150033000", "", "colsize=21 col=lld:lld:lld");
636+
637+
std::ifstream redirectStream(redirectFile.fPath);
638+
std::stringstream redirectOutput;
639+
redirectOutput << redirectStream.rdbuf();
640+
641+
const static std::string expectedScanOut{
642+
R"Scan(************************************************************************************
643+
* Row * x * x-1617047019150033925 * x-1617047019150033000 *
644+
************************************************************************************
645+
* 0 * 1617047019150033926 * 1 * 926 *
646+
************************************************************************************
647+
)Scan"};
648+
EXPECT_EQ(redirectOutput.str(), expectedScanOut);
649+
} else
650+
throw std::runtime_error("Could not retrieve TTreePlayer from main tree!");
651+
}
652+
}

0 commit comments

Comments
 (0)