def si_range(value, default_lower=0):
    """Convert a range of SI numbers to a range of integers.

    The 'value' is a dict containing members 'lower' and 'upper', each
    being an integer or string suitable for si_as_number().  If
    'value' is not a dict, it will be passed directly to
    si_as_number() and treated as a non-range (see below).  If there
    is no 'lower' member and 'default_lower' has been provided, that
    value will be used for the lower number.

    Returns a dict containing memebers 'lower' and 'upper', both
    integers.  For non-ranges, both will be identical.

    Raises ValueError if something doesn't make sense.

    """

    if type(value) in [int, str, unicode]:
        result = si_as_number(value)
        return {"lower": result, "upper": result}

    if type(default_lower) != int:
        raise ValueError("Default lower value must be integer")

    # TODO: Complain about anything else in the input?

    result = {}

    if "lower" not in value:
        # Copy this because altering it would clobber the original (not cool)
        vrange = copy.copy(value)
        vrange["lower"] = default_lower
        value = vrange

    for member in ["lower", "upper"]:
        try:
            result[member] = si_as_number(value[member])
        except KeyError:
            raise ValueError("Missing '%s' in input" % member)

    if result['lower'] > result['upper']:
        raise ValueError("Lower value must be less than upper value")

    return result