From 4944f5f8455e9bdfe9c2fda627b4cac61bc26829 Mon Sep 17 00:00:00 2001 From: ferdymercury Date: Sun, 5 Jul 2026 16:03:11 +0200 Subject: [PATCH 1/5] [tree] do not silently hide sqrt of negative value return nan instead of sqrt(abs(value)) Fixes https://github.com/root-project/root/issues/22755 Was like that since the early beginnigns https://github.com/root-project/root/blame/dd588ebc2c01182af573ea9ac7ce2ddcbc734b6e/treeplayer/src/TTreeFormula.cxx --- tree/treeplayer/src/TTreeFormula.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree/treeplayer/src/TTreeFormula.cxx b/tree/treeplayer/src/TTreeFormula.cxx index f9c645892a408..23ed77c71b8b0 100644 --- a/tree/treeplayer/src/TTreeFormula.cxx +++ b/tree/treeplayer/src/TTreeFormula.cxx @@ -4259,7 +4259,7 @@ T TTreeFormula::EvalInstance(Int_t instance, const char *stringStackArg[]) case kfmod : pos--; tab[pos-1] = fmod_local(tab[pos-1],tab[pos]); continue; case kpow : pos--; tab[pos-1] = TMath::Power(tab[pos-1],tab[pos]); continue; case ksq : tab[pos-1] = tab[pos-1]*tab[pos-1]; continue; - case ksqrt : tab[pos-1] = TMath::Sqrt(TMath::Abs(tab[pos-1])); continue; + case ksqrt : tab[pos-1] = TMath::Sqrt(tab[pos-1]); continue; case kstrstr : pos2 -= 2; pos++;if (strstr(stringStack[pos2],stringStack[pos2+1])) tab[pos-1]=1; else tab[pos-1]=0; From ce5c3b53d03e34b489513d2cee107a6a35e062af Mon Sep 17 00:00:00 2001 From: ferdymercury Date: Sun, 5 Jul 2026 16:05:20 +0200 Subject: [PATCH 2/5] [hist] do not silently hide sqrt of nan number return nan instead of sqrt(abs(value)) for consistency with TTreeFormula --- hist/hist/src/TFormula_v5.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hist/hist/src/TFormula_v5.cxx b/hist/hist/src/TFormula_v5.cxx index 679a28a2d6ea5..0ed1fe608ad96 100644 --- a/hist/hist/src/TFormula_v5.cxx +++ b/hist/hist/src/TFormula_v5.cxx @@ -2764,7 +2764,7 @@ Double_t TFormula::EvalParOld(const Double_t *x, const Double_t *uparams) case kfmod : pos--; tab[pos-1] = fmod(tab[pos-1],tab[pos]); continue; case kpow : pos--; tab[pos-1] = TMath::Power(tab[pos-1],tab[pos]); continue; case ksq : tab[pos-1] = tab[pos-1]*tab[pos-1]; continue; - case ksqrt : tab[pos-1] = TMath::Sqrt(TMath::Abs(tab[pos-1])); continue; + case ksqrt : tab[pos-1] = TMath::Sqrt(tab[pos-1]); continue; case kstrstr : strpos -= 2; pos-=2; pos++; if (strstr(stringStack[strpos],stringStack[strpos+1])) tab[pos-1]=1; From a8cf864d580c5d39a17a123386f11e130265d53b Mon Sep 17 00:00:00 2001 From: ferdymercury Date: Wed, 8 Jul 2026 17:33:32 +0200 Subject: [PATCH 3/5] [tree] add test for neg sqrt Full authorship of guitargeek --- tree/treeplayer/test/regressions.cxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tree/treeplayer/test/regressions.cxx b/tree/treeplayer/test/regressions.cxx index e254dbfd6ac94..11ea9e09160f4 100644 --- a/tree/treeplayer/test/regressions.cxx +++ b/tree/treeplayer/test/regressions.cxx @@ -744,3 +744,21 @@ TEST(TTreeScan, ULong64Precision) EXPECT_EQ(scanToString("colsize=21 col=lld:lld:lld"), expectedScanOut); EXPECT_EQ(scanToString("col=21lld:21lld:21lld"), expectedScanOut); } + +// https://github.com/root-project/root/issues/22755 +// sqrt() of a negative argument used to evaluate to sqrt(abs(x)) instead of NaN +TEST(TTreeFormulaRegressions, SqrtOfNegative) +{ + TTree t("t", "t"); + int x = 0; + t.Branch("x", &x, "x/I"); + t.Fill(); + t.GetEntry(0); + + TTreeFormula tf("tf", "sqrt(-4.0)", &t); + EXPECT_TRUE(std::isnan(tf.EvalInstance())); + TTreeFormula tf2("tf2", "sqrt(x - 4)", &t); + EXPECT_TRUE(std::isnan(tf2.EvalInstance())); + TTreeFormula tf3("tf3", "sqrt(x + 9)", &t); + EXPECT_FLOAT_EQ(tf3.EvalInstance(), 3.); +} From 0a3d3187d7faabf84f23aa7cd87644b46b75f809 Mon Sep 17 00:00:00 2001 From: ferdymercury Date: Wed, 26 Aug 2026 14:49:32 +0200 Subject: [PATCH 4/5] [RelNotes] mention sqrt behavior change fully written by guitargeek --- README/ReleaseNotes/v640/index.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README/ReleaseNotes/v640/index.md b/README/ReleaseNotes/v640/index.md index f57ee7eca65e0..f40245dd898f9 100644 --- a/README/ReleaseNotes/v640/index.md +++ b/README/ReleaseNotes/v640/index.md @@ -217,6 +217,16 @@ This change affects the following classes: `TFile`, `TMapFile`, `TMemFile`, `TD ### TTree +#### Behavior change since 6.40.04: `sqrt()` of negative arguments in TTreeFormula now returns NaN + +Since its introduction in 1995, the formula engine used by `TTree::Draw()`, `TTree::Scan()` and `TTreeFormula` +silently evaluated `sqrt(x)` as `sqrt(abs(x))` for negative arguments (or as `0` in the optimized evaluation path +of the legacy `ROOT::v5::TFormula`). This could produce silently wrong results, e.g. in selections involving +`sqrt` of an expression that can become negative. `sqrt()` now returns NaN for negative arguments, consistent +with `TMath::Sqrt()`, the standard C `sqrt()`, and the modern `TFormula` used by `TF1`. +Note that in a selection, a NaN evaluates as `false`, so entries where the `sqrt` argument is negative now fail +the cut instead of being selected based on `sqrt(abs(x))`. + ### RNTuple - A new API to create "active entry tokens" was added to the `RNTupleReader`. Active entry tokens that are used prevent cache eviction of the entries at hand. From 0ccbf1ea7952057c5b7f3f1e81809178f3dff63c Mon Sep 17 00:00:00 2001 From: ferdymercury Date: Fri, 28 Aug 2026 08:46:15 +0200 Subject: [PATCH 5/5] [cmake] update version in relnotes --- README/ReleaseNotes/v640/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README/ReleaseNotes/v640/index.md b/README/ReleaseNotes/v640/index.md index f40245dd898f9..8eed6e5daee8f 100644 --- a/README/ReleaseNotes/v640/index.md +++ b/README/ReleaseNotes/v640/index.md @@ -217,7 +217,7 @@ This change affects the following classes: `TFile`, `TMapFile`, `TMemFile`, `TD ### TTree -#### Behavior change since 6.40.04: `sqrt()` of negative arguments in TTreeFormula now returns NaN +#### Behavior change since 6.40.06: `sqrt()` of negative arguments in TTreeFormula now returns NaN Since its introduction in 1995, the formula engine used by `TTree::Draw()`, `TTree::Scan()` and `TTreeFormula` silently evaluated `sqrt(x)` as `sqrt(abs(x))` for negative arguments (or as `0` in the optimized evaluation path