def split_ranges(intsize, step, start, end):
    """Splits a range of numbers (from ``start`` to ``end``, inclusive)
    into a sequence of trie ranges of the form ``(start, end, shift)``. The
    consumer of these tuples is expected to shift the ``start`` and ``end``
    right by ``shift``.

    This is used for generating term ranges for a numeric field. The queries
    for the edges of the range are generated at high precision and large blocks
    in the middle are generated at low precision.
    """
    shift = 0
    while True:
        diff = 1 << shift + step
        mask = (1 << step) - 1 << shift
        setbits = lambda x: x | (1 << shift) - 1
        haslower = start & mask != 0
        hasupper = end & mask != mask
        not_mask = ~mask & (1 << intsize + 1) - 1
        nextstart = (start + diff if haslower else start) & not_mask
        nextend = (end - diff if hasupper else end) & not_mask
        if shift + step >= intsize or nextstart > nextend:
            yield start, setbits(end), shift
            break
        if haslower:
            yield start, setbits(start | mask), shift
        if hasupper:
            yield end & not_mask, setbits(end), shift
        start = nextstart
        end = nextend
        shift += step
