Skip to content

Commit 7f33a48

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 0f89185 commit 7f33a48

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

hist/hist/inc/TAxis.h

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

5555
Bool_t HasBinWithoutLabel() const;
56+
Int_t DoFindFixBin(Double_t x) const;
5657

5758

5859
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
@@ -287,32 +287,25 @@ void TAxis::ExecuteEvent(Int_t event, Int_t px, Int_t py)
287287

288288
Int_t TAxis::FindBin(Double_t x)
289289
{
290-
Int_t bin;
291290
// NOTE: This should not be allowed for Alphanumeric histograms,
292291
// but it is heavily used (legacy) in the TTreePlayer to fill alphanumeric histograms.
293292
// but in case of alphanumeric do-not extend the axis. It makes no sense
294293
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.");
295294
if (x < fXmin) { //*-* underflow
296-
bin = 0;
295+
Int_t bin = 0;
297296
if (fParent == nullptr) return bin;
298297
if (!CanExtend() || IsAlphanumeric() ) return bin;
299298
((TH1*)fParent)->ExtendAxis(x,this);
300299
return FindFixBin(x);
301300
} else if ( !(x < fXmax)) { //*-* overflow (note the way to catch NaN)
302-
bin = fNbins+1;
301+
Int_t bin = fNbins+1;
303302
if (fParent == nullptr) return bin;
304303
if (!CanExtend() || IsAlphanumeric() ) return bin;
305304
((TH1*)fParent)->ExtendAxis(x,this);
306305
return FindFixBin(x);
307-
} else {
308-
if (!fXbins.fN) { //*-* fix bins
309-
bin = 1 + int (fNbins*(x-fXmin)/(fXmax-fXmin) );
310-
} else { //*-* variable bin sizes
311-
//for (bin =1; x >= fXbins.fArray[bin]; bin++);
312-
bin = 1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);
313-
}
314306
}
315-
return bin;
307+
return DoFindFixBin(x);
308+
316309
}
317310

318311
////////////////////////////////////////////////////////////////////////////////
@@ -404,6 +397,28 @@ Int_t TAxis::FindFixBin(const char *label) const
404397
return -1;
405398
}
406399

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

408423
////////////////////////////////////////////////////////////////////////////////
409424
/// Find bin number corresponding to abscissa x
@@ -413,18 +428,13 @@ Int_t TAxis::FindFixBin(const char *label) const
413428

414429
Int_t TAxis::FindFixBin(Double_t x) const
415430
{
416-
Int_t bin;
431+
Int_t bin = 0;
417432
if (x < fXmin) { //*-* underflow
418433
bin = 0;
419434
} else if ( !(x < fXmax)) { //*-* overflow (note the way to catch NaN
420435
bin = fNbins+1;
421436
} else {
422-
if (!fXbins.fN) { //*-* fix bins
423-
bin = 1 + int (fNbins*(x-fXmin)/(fXmax-fXmin) );
424-
} else { //*-* variable bin sizes
425-
// for (bin =1; x >= fXbins.fArray[bin]; bin++);
426-
bin = 1 + TMath::BinarySearch(fXbins.fN,fXbins.fArray,x);
427-
}
437+
bin = DoFindFixBin(x);
428438
}
429439
return bin;
430440
}

hist/hist/test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ ROOT_ADD_GTEST(testTHn THn.cxx LIBRARIES Hist Matrix MathCore RIO)
1212
ROOT_ADD_GTEST(testTH1 test_TH1.cxx LIBRARIES Hist)
1313
ROOT_ADD_GTEST(testTHStack test_THStack.cxx LIBRARIES Hist)
1414
ROOT_ADD_GTEST(testProject3Dname test_Project3D_name.cxx LIBRARIES Hist)
15+
ROOT_ADD_GTEST(testTAxisFindBin test_TAxis_FindBin.cxx LIBRARIES Hist)
1516
ROOT_ADD_GTEST(testTFormula test_TFormula.cxx LIBRARIES Hist)
1617
ROOT_ADD_GTEST(testTKDE test_tkde.cxx LIBRARIES Hist)
1718
ROOT_ADD_GTEST(testTH1FindFirstBinAbove test_TH1_FindFirstBinAbove.cxx LIBRARIES Hist)

hist/hist/test/test_TAxis_FindBin.cxx

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
EXPECT_EQ(i+1, ax.FindFixBin(x));
17+
x -= x * std::numeric_limits<double>::epsilon();
18+
EXPECT_EQ(i, ax.FindBin(x));
19+
}
20+
}
21+
TEST(TAxis, FindBinApprox)
22+
{
23+
TAxis ax(90, 0. , 10.);
24+
for (int i = 1; i <= ax.GetNbins(); i++) {
25+
double x = ax.GetBinLowEdge(i);
26+
EXPECT_EQ(i, ax.FindBin(x));
27+
EXPECT_EQ(i, ax.FindFixBin(x));
28+
x = ax.GetBinUpEdge(i);
29+
EXPECT_EQ(i+1, ax.FindBin(x));
30+
EXPECT_EQ(i+1, ax.FindFixBin(x));
31+
x -= x * std::numeric_limits<double>::epsilon();
32+
EXPECT_EQ(i, ax.FindBin(x));
33+
}
34+
}

0 commit comments

Comments
 (0)