Given start, stop, step and array length, return
    absolute values of start, stop, and step for generating index range.
    The returned values have been compensated by adding length if they
    are less than zero for all the cases but slice(None, None, -1).
    Note that the returned value of stop is not necessarily >= 0, since
    absolute stop is -1 in the case of slice(None, None, -1).

def _get_index_range(start, stop, length, step=1):
    """Given start, stop, step and array length, return
    absolute values of start, stop, and step for generating index range.
    The returned values have been compensated by adding length if they
    are less than zero for all the cases but slice(None, None, -1).
    Note that the returned value of stop is not necessarily >= 0, since
    absolute stop is -1 in the case of slice(None, None, -1)."""
    if step == 0:
        raise ValueError('step size cannot be zero')
    if length < 0:
        raise ValueError('array length cannot be less than zero')
    if step is None:
        step = 1
    if start is None:
        if step > 0:
            start = 0
        else:
            start = length - 1
    elif start < 0:
        start += length
        if start < 0:
            raise IndexError('Slicing start %d exceeds limit of %d' % (start-length, length))
    elif start >= length:
        raise IndexError('Slicing start %d exceeds limit of %d' % (start, length))

    if stop is None:
        if step > 0:
            stop = length
        else:
            # this supports case such as ::-1
            # stop = -1 here refers to the element before index 0,
            # instead of the last element in the array
            stop = -1
    elif stop < 0:
        stop += length
        if stop < 0:
            raise IndexError('Slicing stop %d exceeds limit of %d' % (stop-length, length))
    elif stop > length:
        raise IndexError('Slicing stop %d exceeds limit of %d' % (stop, length))

    return start, stop, step