|
19 | 19 |
|
20 | 20 | #include "gtest/gtest.h" |
21 | 21 |
|
| 22 | +#include <limits> |
22 | 23 | #include <string> |
23 | 24 | #include <vector> |
24 | 25 | #include <fstream> |
@@ -585,3 +586,77 @@ TEST(TTreeScan, TTreeGetBranchOfFriendTChain) |
585 | 586 | throw std::runtime_error("Could not retrieve TTreePlayer from main tree!"); |
586 | 587 | } |
587 | 588 | } |
| 589 | + |
| 590 | +// https://github.com/root-project/root/issues/7844 |
| 591 | +// TTree::Scan() used to lose precision when printing 64-bit integer branches |
| 592 | +// (e.g. ULong64_t): the "lld" column format, when given without an embedded |
| 593 | +// column size (i.e. via "colsize=N col=lld"), was not recognized as a |
| 594 | +// "long long" modifier because of an off-by-one in the length-modifier |
| 595 | +// detection in TTreeFormula::PrintValue. As a consequence the value was |
| 596 | +// evaluated and printed as a double, rounding anything above 2^53. |
| 597 | +TEST(TTreeScan, ULong64Precision) |
| 598 | +{ |
| 599 | + // The "long long" Scan/Draw column format is evaluated through `long double` |
| 600 | + // (see TTreeFormula::PrintValue), so exact 64-bit integer output is only |
| 601 | + // possible where `long double` has more mantissa bits than `double`. That is |
| 602 | + // the case on x86-64 (80-bit, 64-bit mantissa) but not, e.g., on macOS ARM |
| 603 | + // where `long double` is just a 64-bit `double` (53-bit mantissa). Skip the |
| 604 | + // exactness check there, since the value genuinely cannot be represented. |
| 605 | + if (std::numeric_limits<long double>::digits <= std::numeric_limits<double>::digits) |
| 606 | + GTEST_SKIP() << "long double is not wider than double on this platform"; |
| 607 | + |
| 608 | + // 1617047019150033926 needs 61 bits, so it cannot be represented exactly |
| 609 | + // by a double (53-bit mantissa). |
| 610 | + constexpr ULong64_t value{1617047019150033926ULL}; |
| 611 | + |
| 612 | + struct DatasetRAII { |
| 613 | + const char *fTreeName{"tree_7844"}; |
| 614 | + const char *fFileName{"tree_7844.root"}; |
| 615 | + DatasetRAII(ULong64_t v) |
| 616 | + { |
| 617 | + auto file = std::make_unique<TFile>(fFileName, "recreate"); |
| 618 | + auto tree = std::make_unique<TTree>(fTreeName, fTreeName); |
| 619 | + |
| 620 | + ULong64_t x = v; |
| 621 | + tree->Branch("x", &x, "x/l"); |
| 622 | + tree->Fill(); |
| 623 | + file->Write(); |
| 624 | + } |
| 625 | + |
| 626 | + ~DatasetRAII() { std::remove(fFileName); } |
| 627 | + } dataset{value}; |
| 628 | + |
| 629 | + auto file = std::make_unique<TFile>(dataset.fFileName); |
| 630 | + std::unique_ptr<TTree> tree{file->Get<TTree>(dataset.fTreeName)}; |
| 631 | + |
| 632 | + std::ostringstream strCout; |
| 633 | + { |
| 634 | + if (auto *treePlayer = static_cast<TTreePlayer *>(tree->GetPlayer())) { |
| 635 | + struct FileRAII { |
| 636 | + const char *fPath; |
| 637 | + FileRAII(const char *name) : fPath(name) {} |
| 638 | + ~FileRAII() { std::remove(fPath); } |
| 639 | + } redirectFile{"tree_7844_regression_redirect.txt"}; |
| 640 | + treePlayer->SetScanRedirect(true); |
| 641 | + treePlayer->SetScanFileName(redirectFile.fPath); |
| 642 | + // The "lld" format without an embedded size (the size is given |
| 643 | + // separately via "colsize=") must print the exact 64-bit value, as |
| 644 | + // well as the exact result of integer arithmetic with large constants. |
| 645 | + tree->Scan("x:x-1617047019150033925:x-1617047019150033000", "", "colsize=21 col=lld:lld:lld"); |
| 646 | + |
| 647 | + std::ifstream redirectStream(redirectFile.fPath); |
| 648 | + std::stringstream redirectOutput; |
| 649 | + redirectOutput << redirectStream.rdbuf(); |
| 650 | + |
| 651 | + const static std::string expectedScanOut{ |
| 652 | + R"Scan(************************************************************************************ |
| 653 | +* Row * x * x-1617047019150033925 * x-1617047019150033000 * |
| 654 | +************************************************************************************ |
| 655 | +* 0 * 1617047019150033926 * 1 * 926 * |
| 656 | +************************************************************************************ |
| 657 | +)Scan"}; |
| 658 | + EXPECT_EQ(redirectOutput.str(), expectedScanOut); |
| 659 | + } else |
| 660 | + throw std::runtime_error("Could not retrieve TTreePlayer from main tree!"); |
| 661 | + } |
| 662 | +} |
0 commit comments