Here is a patch that merges the two processing loops into one (deduplicating some logic between them), and also makes the tail feerate explicit (even though only FeeFrac{0, 1} is used for now).
No need to take this, but in case people find it simpler to review a generic approach, perhaps it's useful.
diff --git a/src/util/feefrac.cpp b/src/util/feefrac.cpp
index a970adedc30..569f32f4615 100644
--- a/src/util/feefrac.cpp
+++ b/src/util/feefrac.cpp
@@ -22,7 +22,7 @@ void BuildDiagramFromUnsortedChunks(std::vector<FeeFrac>& chunks, std::vector<Fe
}
}
-std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1)
+std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1, FeeFrac tail_feerate)
{
/** Array to allow indexed access to input diagrams. */
const std::array<Span<const FeeFrac>, 2> dias = {dia0, dia1};
@@ -40,51 +40,47 @@ std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const
Assert(prev_point(0).IsEmpty());
Assert(prev_point(1).IsEmpty());
- // Compare the overlapping area of the diagrams.
- while (next_index[0] < dias[0].size() && next_index[1] < dias[1].size()) {
- // Determine which diagram has the first unprocessed point.
- const int unproc_side = next_point(0).size > next_point(1).size;
+ do {
+ bool done_0 = next_index[0] == dias[0].size();
+ bool done_1 = next_index[1] == dias[1].size();
+ if (done_0 && done_1) break;
+
+ // Determine which diagram has the first unprocessed point. If one side is finished, use the
+ // other one.
+ const int unproc_side = (done_0 || done_1) ? done_0 : next_point(0).size > next_point(1).size;
// let `P` be the next point on diagram unproc_side, and `A` and `B` the previous and next points
// on the other diagram. We want to know if P lies above or below the line AB. To determine this, we
- // compute the direction coefficients of line AB and of line AP, and compare them. These
- // direction coefficients are fee per size, and can thus be expressed as FeeFracs.
+ // compute the slopes of line AB and of line AP, and compare them. These slopes are fee per size,
+ // and can thus be expressed as FeeFracs.
const FeeFrac& point_p = next_point(unproc_side);
const FeeFrac& point_a = prev_point(!unproc_side);
- const FeeFrac& point_b = next_point(!unproc_side);
- const auto coef_ab = point_b - point_a;
- const auto coef_ap = point_p - point_a;
- Assume(coef_ap.size > 0);
- Assume(coef_ab.size >= coef_ap.size);
- // Perform the comparison. If P lies above AB, unproc_side is better in P. If P lies below
- // AB, then !unproc_side is better in P.
- const auto cmp = FeeRateCompare(coef_ap, coef_ab);
+ const auto slope_ap = point_p - point_a;
+ Assume(slope_ap.size > 0);
+ std::weak_ordering cmp = std::weak_ordering::equivalent;
+ if (!(done_0 || done_1)) {
+ // If both sides have points left, compute B, and the slope of AB explicitly.
+ const FeeFrac& point_b = next_point(!unproc_side);
+ const auto slope_ab = point_b - point_a;
+ Assume(slope_ab.size >= slope_ap.size);
+ cmp = FeeRateCompare(slope_ap, slope_ab);
+
+ // If B and P have the same size, B can be marked as processed (in addition to P, see
+ // below), as we've already performed a comparison at this size.
+ if (point_b.size == point_p.size) ++next_index[!unproc_side];
+ } else {
+ // If one of the sides has no points left, act as if AB has slope tail_feerate.
+ cmp = FeeRateCompare(slope_ap, tail_feerate);
+ }
+
+ // If P lies above AB, unproc_side is better in P. If P lies below AB, then !unproc_side is
+ // better in P.
if (std::is_gt(cmp)) better_somewhere[unproc_side] = true;
if (std::is_lt(cmp)) better_somewhere[!unproc_side] = true;
- // Mark P as processed. If B and P have the same size, B can also be marked as processed as
- // we've already performed a comparison at this size.
- ++next_index[unproc_side];
- if (point_b.size == point_p.size) ++next_index[!unproc_side];
- }
-
- // Tail check at 0 feerate: Compare the remaining area. Use similar logic as in the loop above,
- // except we use a horizontal line instead of AB, as no point B exists anymore.
- const int long_side = next_index[1] != dias[1].size();
- Assume(next_index[!long_side] == dias[!long_side].size());
- // The point A now remains fixed: the last point of the shorter diagram.
- const FeeFrac& point_a = prev_point(!long_side);
- while (next_index[long_side] < dias[long_side].size()) {
- // Compare AP (where P is the next unprocessed point on the longer diagram) with a horizontal line
- // extending infinitely from A. This is equivalent to checking the sign of the fee of P-A.
- const FeeFrac& point_p = next_point(long_side);
- const auto coef_ap = point_p - point_a;
- const auto cmp = coef_ap.fee <=> 0;
- if (std::is_gt(cmp)) better_somewhere[long_side] = true;
- if (std::is_lt(cmp)) better_somewhere[!long_side] = true;
// Mark P as processed.
- ++next_index[long_side];
- }
+ ++next_index[unproc_side];
+ } while(true);
// If both diagrams are better somewhere, they are incomparable.
if (better_somewhere[0] && better_somewhere[1]) return std::partial_ordering::unordered;
diff --git a/src/util/feefrac.h b/src/util/feefrac.h
index 12698a4a653..706facc34b0 100644
--- a/src/util/feefrac.h
+++ b/src/util/feefrac.h
@@ -156,7 +156,7 @@ struct FeeFrac
void BuildDiagramFromUnsortedChunks(std::vector<FeeFrac>& chunks, std::vector<FeeFrac>& diagram);
/** Compares two feerate diagrams (which must both start at size=0). The shorter one is implicitly
- * extended with a horizontal straight line. */
-std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1);
+ * extended with an infinite line whose slope equals tail_feefrac (by default: horizontal). */
+std::partial_ordering CompareFeerateDiagram(Span<const FeeFrac> dia0, Span<const FeeFrac> dia1, FeeFrac tail_feerate={0, 1});
#endif // BITCOIN_UTIL_FEEFRAC_H