Skip to content

Commit 125c995

Browse files
committed
[hist] Improve calculation of TAxis::FindBin(x) when x is at the bin edges
When x is at the bin edge values numerical error can cause the computation of the bin to be the one before (or after). Correct for this case assuming the bin edge are computed as in `TH1::getBinLowEdge` and `TH1::GetBinUpEdge`. It is clear that the bin edge values are represented as floating point, so depending on how they are computed they can be sometime different. However, it is better to have teh consistency to return the correct value when computed as internally in TAxis. This should fix teh problem reported in https://root-forum.cern.ch/t/bug-in-taxis-findbin/57210 and #14091
1 parent cd55619 commit 125c995

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

hist/hist/inc/TAxis.h

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class TAxis : public TNamed, public TAttAxis {
5252
};
5353

5454
Bool_t HasBinWithoutLabel() const;
55+
Int_t DoFindFixBin(Double_t x) const;
5556

5657

5758
TAxisModLab *FindModLab(Int_t num, Double_t v = 0., Double_t eps = 0.) const;

hist/hist/src/TAxis.cxx

+28-18
Original file line numberDiff line numberDiff line change
@@ -292,32 +292,25 @@ void TAxis::ExecuteEvent(Int_t event, Int_t px, Int_t py)
292292

293293
Int_t TAxis::FindBin(Double_t x)
294294
{
295-
Int_t bin;
296295
// NOTE: This should not be allowed for Alphanumeric histograms,
297296
// but it is heavily used (legacy) in the TTreePlayer to fill alphanumeric histograms.
298297
// but in case of alphanumeric do-not extend the axis. It makes no sense
299298
if (IsAlphanumeric() && gDebug) Info("FindBin","Numeric query on alphanumeric axis - Sorting the bins or extending the axes / rebinning can alter the correspondence between the label and the bin interval.");
300299
if (x < fXmin) { //*-* underflow
301-
bin = 0;
300+
Int_t bin = 0;
302301
if (fParent == nullptr) return bin;
303302
if (!CanExtend() || IsAlphanumeric() ) return bin;
304303
((TH1*)fParent)->ExtendAxis(x,this);
305304
return FindFixBin(x);
306305
} else if ( !(x < fXmax)) { //*-* overflow (note the way to catch NaN)
307-
bin = fNbins+1;
306+
Int_t bin = fNbins+1;
308307
if (fParent == nullptr) return bin;
309308
if (!CanExtend() || IsAlphanumeric() ) return bin;
310309
((TH1*)fParent)->ExtendAxis(x,this);
311310
return FindFixBin(x);
312-
} else {
313-
if (!fXbins.fN) { //*-* fix bins
314-
bin = 1 + int (fNbins*(x-fXmin)/(fXmax-fXmin) );
315-
} else { //*-* variable bin sizes
316-
//for (bin =1; x >= fXbins.fArray[bin]; bin++);
317-
bin = 1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);
318-
}
319311
}
320-
return bin;
312+
return DoFindFixBin(x);
313+
321314
}
322315

323316
////////////////////////////////////////////////////////////////////////////////
@@ -409,6 +402,28 @@ Int_t TAxis::FindFixBin(const char *label) const
409402
return -1;
410403
}
411404

405+
////////////////////////////////////////////////////////////////////////////////
406+
/// Internal function to find the fix bin
407+
////////////////////////////////////////////////////////////////////////////////
408+
Int_t TAxis::DoFindFixBin(Double_t x) const
409+
{
410+
int bin = 0;
411+
if (!fXbins.fN) { //*-* fix bins
412+
bin = 1 + int (fNbins * (x-fXmin)/(fXmax-fXmin) );
413+
// apply correction when x is at the bin edge value
414+
// (computed as in GetBinLowEdge) a numerical error in the
415+
// above division can cause a migration in a neighbour bin.
416+
double binwidth = (fXmax - fXmin) / Double_t(fNbins);
417+
double upEdge = fXmin + (bin) * binwidth;
418+
double lowEdge = fXmin + (bin - 1) * binwidth;
419+
if (upEdge <= x) bin += 1;
420+
if (lowEdge > x) bin -= 1;
421+
} else { //*-* variable bin sizes
422+
//for (bin =1; x >= fXbins.fArray[bin]; bin++);
423+
bin = 1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);
424+
}
425+
return bin;
426+
}
412427

413428
////////////////////////////////////////////////////////////////////////////////
414429
/// Find bin number corresponding to abscissa x
@@ -418,18 +433,13 @@ Int_t TAxis::FindFixBin(const char *label) const
418433

419434
Int_t TAxis::FindFixBin(Double_t x) const
420435
{
421-
Int_t bin;
436+
Int_t bin = 0;
422437
if (x < fXmin) { //*-* underflow
423438
bin = 0;
424439
} else if ( !(x < fXmax)) { //*-* overflow (note the way to catch NaN
425440
bin = fNbins+1;
426441
} else {
427-
if (!fXbins.fN) { //*-* fix bins
428-
bin = 1 + int (fNbins*(x-fXmin)/(fXmax-fXmin) );
429-
} else { //*-* variable bin sizes
430-
// for (bin =1; x >= fXbins.fArray[bin]; bin++);
431-
bin = 1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);
432-
}
442+
bin = DoFindFixBin(x);
433443
}
434444
return bin;
435445
}

hist/hist/test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ROOT_ADD_GTEST(testTH2PolyBinError test_TH2Poly_BinError.cxx LIBRARIES Hist Matr
99
ROOT_ADD_GTEST(testTH2PolyAdd test_TH2Poly_Add.cxx LIBRARIES Hist Matrix MathCore RIO)
1010
ROOT_ADD_GTEST(testTHn THn.cxx LIBRARIES Hist Matrix MathCore RIO)
1111
ROOT_ADD_GTEST(testTH1 test_TH1.cxx LIBRARIES Hist)
12+
ROOT_ADD_GTEST(testTAxisFindBin test_TAxis_FindBin.cxx LIBRARIES Hist)
1213
ROOT_ADD_GTEST(testTFormula test_TFormula.cxx LIBRARIES Hist)
1314
ROOT_ADD_GTEST(testTKDE test_tkde.cxx LIBRARIES Hist)
1415
ROOT_ADD_GTEST(testTH1FindFirstBinAbove test_TH1_FindFirstBinAbove.cxx LIBRARIES Hist)

hist/hist/test/test_TAxis_FindBin.cxx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "gtest/gtest.h"
2+
3+
#include "TAxis.h"
4+
5+
6+
TEST(TAxis, FindBinExact)
7+
{
8+
//Test the case where bin edges are exactly represented as floating points
9+
TAxis ax(88, 1010, 1098);
10+
for (int i = 1; i <= ax.GetNbins(); i++) {
11+
double x = ax.GetBinLowEdge(i);
12+
EXPECT_EQ(i, ax.FindBin(x));
13+
EXPECT_EQ(i, ax.FindFixBin(x));
14+
x = ax.GetBinUpEdge(i);
15+
EXPECT_EQ(i+1, ax.FindBin(x));
16+
x -= x * std::numeric_limits<double>::epsilon();
17+
EXPECT_EQ(i, ax.FindBin(x));
18+
}
19+
}
20+
TEST(TAxis, FindBinApprox)
21+
{
22+
TAxis ax(90, 0. , 10.);
23+
for (int i = 1; i <= ax.GetNbins(); i++) {
24+
double x = ax.GetBinLowEdge(i);
25+
EXPECT_EQ(i, ax.FindBin(x));
26+
EXPECT_EQ(i, ax.FindFixBin(x));
27+
x = ax.GetBinUpEdge(i);
28+
EXPECT_EQ(i+1, ax.FindBin(x));
29+
x -= x * std::numeric_limits<double>::epsilon();
30+
EXPECT_EQ(i, ax.FindBin(x));
31+
}
32+
}

0 commit comments

Comments
 (0)