Repository: cta-observatory/pyirf

Issue:
`create_bins_per_decade` does not include the maximum value
binning edges obtained using `create_bins_per_decade` does not include the maximum value

```
>>> import astropy.units as u
>>> from pyirf.binning import create_bins_per_decade
>>> 
>>> energy_min = 0.01 * u.TeV
>>> energy_max = 100 * u.TeV
>>> create_bins_per_decade(energy_min, energy_max)
<Quantity [1.00000000e-02, 1.58489319e-02, 2.51188643e-02, 3.98107171e-02,
           6.30957344e-02, 1.00000000e-01, 1.58489319e-01, 2.51188643e-01,
           3.98107171e-01, 6.30957344e-01, 1.00000000e+00, 1.58489319e+00,
           2.51188643e+00, 3.98107171e+00, 6.30957344e+00, 1.00000000e+01,
           1.58489319e+01, 2.51188643e+01, 3.98107171e+01, 6.30957344e+01] TeV>
```

I think it would be better to implement like gammapy, what do you think?
https://docs.gammapy.org/0.20.1/_modules/gammapy/maps/axes.html#MapAxis.from_energy_bounds

Discussion:

Patch:
diff --git a/pyirf/binning.py b/pyirf/binning.py
index bbc4e5f..f23cd0e 100644
--- a/pyirf/binning.py
+++ b/pyirf/binning.py
@@ -98,7 +98,9 @@ def create_bins_per_decade(e_min, e_max, bins_per_decade=5):
     e_min: u.Quantity[energy]
         Minimum energy, inclusive
     e_max: u.Quantity[energy]
-        Maximum energy, exclusive
+        Maximum energy, non-inclusive
+        If the endpoint exactly matches the ``n_bins_per_decade`` requirement,
+        it will be included.
     n_bins_per_decade: int
         number of bins per decade
 
@@ -112,7 +114,10 @@ def create_bins_per_decade(e_min, e_max, bins_per_decade=5):
     log_lower = np.log10(e_min.to_value(unit))
     log_upper = np.log10(e_max.to_value(unit))
 
-    bins = 10 ** np.arange(log_lower, log_upper, 1 / bins_per_decade)
+    step = 1 / bins_per_decade
+    # include endpoint if reasonably close
+    eps = step / 10000
+    bins = 10 ** np.arange(log_lower, log_upper + eps, step)
     return u.Quantity(bins, e_min.unit, copy=False)
 
 


Test patch:
diff --git a/pyirf/tests/test_binning.py b/pyirf/tests/test_binning.py
index bdc17cc..2f8797c 100644
--- a/pyirf/tests/test_binning.py
+++ b/pyirf/tests/test_binning.py
@@ -45,18 +45,25 @@ def test_bins_per_decade():
     bins = create_bins_per_decade(100 * u.GeV, 100 * u.TeV)
 
     assert bins.unit == u.GeV
-    assert len(bins) == 15  # end non-inclusive
+    assert len(bins) == 16  # end inclusive if exactly fits per_decade
 
     assert bins[0] == 100 * u.GeV
     assert np.allclose(np.diff(np.log10(bins.to_value(u.GeV))), 0.2)
 
     bins = create_bins_per_decade(100 * u.GeV, 100 * u.TeV, 10)
     assert bins.unit == u.GeV
-    assert len(bins) == 30  # end non-inclusive
+    assert len(bins) == 31  # end inclusive since it fits exactly
 
     assert bins[0] == 100 * u.GeV
     assert np.allclose(np.diff(np.log10(bins.to_value(u.GeV))), 0.1)
 
+    bins = create_bins_per_decade(100 * u.GeV, 105 * u.TeV, 5)
+    assert bins.unit == u.GeV
+    assert len(bins) == 16  # end non-inclusive
+
+    assert u.isclose(bins[-1], 100 * u.TeV) # last value at correct difference
+    assert np.allclose(np.diff(np.log10(bins.to_value(u.GeV))), 0.2)
+
 
 def test_create_histogram_table():
     '''Test create histogram table'''