## Scalar functions on intervals (no directed rounding used)

mag(a::Interval) = max( abs(a.lo), abs(a.hi) )
mag(::EmptyInterval{T}) where T = T(NaN)

function mig(a::Interval{T}) where T
    zero_in(a) && return zero(T)
    r1, r2 = (@round_down(abs(a.lo)), @round_down(abs(a.hi)))
    min( r1, r2 )
end
mig(::EmptyInterval{T}) where T = T(NaN)

# Infimum and supremum of an interval
inf(a::Interval{T}) where T =
    ifelse(iszero(a.lo), -zero(T), a.lo) # IEEE 1788-2015 requires negative sign
sup(a::Interval{T}) where T =
    ifelse(iszero(a.hi), zero(T), a.hi) # IEEE 1788-2015 requires positive sign

# by convention, empty intervals return values which imply emptiness
inf(a::EmptyInterval{T}) where T = T(Inf)
sup(a::EmptyInterval{T}) where T = T(-Inf)

# mid, diam, radius

#= Fallback in case of overflow: a.hi + a.lo == +Inf or a.hi + a.lo == -Inf.
    This case can not be the default one as it does not pass several
    IEEE1788-2015 tests for small floats.
=#
midpoint_bounded_safe(a::Interval) = 0.5a.lo + 0.5a.hi
midpoint_bounded_accurate(a::Interval{T}) where T = 0.5(a.lo + a.hi) |>
    x -> ifelse(iszero(x), zero(T), x) # ensure positive sign for zero

# NOTE: mid could be renamed to mean? or median?
# Compare pg. 64 of the IEEE 1788-2015 standard:
"""
    mid(a::Interval)

Find the midpoint of interval `a`.

For intervals of the form `[-Inf, x]` or `[x, +Inf]` where `x` is finite, return
respectively `nextfloat(-Inf)` and `prevfloat(+Inf)`. Note that it differs from the
behavior of `mid(a, α=0.5)`.
"""
function mid(a::Interval{T}) where T
    isentire(a) && return zero(T)
    a.lo == -Inf && return nextfloat(a.lo)
    a.hi == +Inf && return prevfloat(a.hi)

    accurate_midpoint = midpoint_bounded_accurate(a)
    if isfinite(accurate_midpoint)
        return accurate_midpoint
    else
        return midpoint_bounded_safe(a)
    end
end

mid(::EmptyInterval{T}) where T = T(NaN)

"""
    diam(a::Interval)

Return the diameter (length) of the `Interval` `a`.
"""
diam(a::Interval{T}) where T = @round_up(a.hi - a.lo) # cf page 64 of IEEE1788
diam(::EmptyInterval{T}) where T = T(NaN)

# Should `radius` this yield diam(a)/2? This affects other functions!
"""
    radius(a::Interval)

Return the radius of the `Interval` `a`, such that
`a ⊆ m ± radius`, where `m = mid(a)` is the midpoint.
"""
function radius(a::Interval{T}) where T
    m = mid(a)
    return max(m - a.lo, a.hi - m)
end
radius(::EmptyInterval{T}) where T = T(NaN)

# midpoint-radius forms
midpoint_radius(a::ValidInterval) = (mid(a), radius(a))
