docs: private void splitExpectedOwnedSegments(Collection<? extends Location> locations, float totalOwnedSegments,
                                             float totalCapacity) {
        float remainingCapacity = totalCapacity;
        float remainingOwned = totalOwnedSegments;
  
        // First pass, assign expected owned segments for locations with too little capacity
        // We know we can do it without a loop because locations are ordered descending by capacity
        List<Location> remainingLocations = new ArrayList<>(locations);
        for (ListIterator<Location> it = remainingLocations.listIterator(locations.size()); it.hasPrevious(); ) {
           Location location = it.previous();
  
           if (remainingOwned < numSegments * remainingLocations.size())
              break;
  
           // We don't have enough locations, so each location must own at least numSegments segments
           int minOwned = numSegments;
           float locationOwned = remainingOwned * location.totalCapacity / remainingCapacity;
           if (locationOwned > minOwned)
              break;
  
           splitExpectedOwnedSegments2(location.getChildren(), minOwned, location.totalCapacity);
           remainingCapacity -= location.totalCapacity;
           remainingOwned -= location.expectedOwnedSegments;
           it.remove();
        }
  
        // Second pass, assign expected owned segments for locations with too much capacity
        // We know we can do it without a loop because locations are ordered descending by capacity
        for (Iterator<? extends Location> it = remainingLocations.iterator(); it.hasNext(); ) {
           Location location = it.next();
  
           float maxOwned = computeMaxOwned(remainingOwned, remainingLocations.size());
  
           float locationOwned = remainingOwned * location.totalCapacity / remainingCapacity;
           if (locationOwned < maxOwned)
              break;
  
           splitExpectedOwnedSegments2(location.getChildren(), maxOwned, location.totalCapacity);
           remainingCapacity -= location.totalCapacity;
           remainingOwned -= maxOwned;
           it.remove();
        }
  
        // If there were exactly numSegments segments per location, we're finished here
        if (remainingLocations.isEmpty())
           return;
  
        // Third pass: If more than numSegments segments per location, split segments between their children
        // Else spread remaining segments based only on the capacity, rounding down
        if (remainingLocations.size() * numSegments < remainingOwned) {
           List<Location> childrenLocations = new ArrayList<>(remainingLocations.size() * 2);
           for (Location location : remainingLocations) {
              childrenLocations.addAll(location.getChildren());
           }
           Collections.sort(childrenLocations);
           splitExpectedOwnedSegments2(childrenLocations, remainingOwned, remainingCapacity);
        } else {
           // The allocation algorithm can assign more segments to nodes, so it's ok to miss some segments here
           float fraction = remainingOwned / remainingCapacity;
           for (Location location : remainingLocations) {
              float locationOwned = location.totalCapacity * fraction;
              splitExpectedOwnedSegments2(location.getChildren(), locationOwned, location.totalCapacity);
           }
        }
     }