diff --git a/README/ReleaseNotes/v642/index.md b/README/ReleaseNotes/v642/index.md index 7cf454ef8f1b2..a6d2db59ca4dd 100644 --- a/README/ReleaseNotes/v642/index.md +++ b/README/ReleaseNotes/v642/index.md @@ -75,6 +75,18 @@ maps) will now obtain different, mathematically consistent values. ## Math +## Trees + +### Behavior change: `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))`. + ## RooFit ### Removal of the the constant term optimization for legacy test statistic classes 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; 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; 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.); +}