Repository: JuliaLang/julia
File: test/testhelpers/ULPError.jl (Julia)

Code before:

module ULPError
    export ulp_error, ulp_error_maximum
    @noinline function throw_invalid()
        throw(ArgumentError("invalid"))
    end
    function ulp_error(accurate::AbstractFloat, approximate::AbstractFloat)
        # the ULP error is usually not required to great accuracy, so `Float32` should be precise enough
        zero_return = Float32(0)
        inf_return = Float32(Inf)
        # handle floating-point edge cases
        accur_is_zero = iszero(accurate)
        accur_is_nan = isnan(accurate)
        accur_is_inf = isinf(accurate)
        approx_is_inf = isinf(approximate)
        approx_is_nan = isnan(approximate)
        approx_is_zero = iszero(approximate)
        if accur_is_nan || approx_is_nan
            return if accur_is_nan === approx_is_nan
                zero_return
            else
                inf_return
            end
        end
        if approx_is_inf
            return if accur_is_inf && (signbit(accurate) == signbit(approximate))
                zero_return
            else
                inf_return
            end
        end
        # assuming `precision(BigFloat)` is great enough
        acc = if accurate isa BigFloat
            accurate
        else
            BigFloat(accurate)::BigFloat
        end
        err = abs(Float32((approximate - acc) / eps(approximate))::Float32)
        if isnan(err)
            @noinline throw_invalid()  # unexpected
        end
        err
    end
    function ulp_error(accurate::Acc, approximate::App, x::AbstractFloat) where {Acc, App}
        acc = accurate(x)
        app = approximate(x)
        ulp_error(acc, app)
    end
    function ulp_error(func::Func, x::AbstractFloat) where {Func}
        ulp_error(func ∘ BigFloat, func, x)
    end

Review comment (suggestion):
maybe put all of this inside `!(isfinite(approx) && isfinite(accurate))`? Also, I don't think these variables are used enough to be worthwhile.

Code after:

module ULPError
    export ulp_error, ulp_error_maximum
    function ulp_error(accurate::AbstractFloat, approximate::AbstractFloat)
        # the ULP error is usually not required to great accuracy, so `Float32` should be precise enough
        zero_return = 0f0
        inf_return = Inf32
        # handle floating-point edge cases
        if !(isfinite(accurate) && isfinite(approximate))
            accur_is_nan = isnan(accurate)
            approx_is_nan = isnan(approximate)
            if accur_is_nan || approx_is_nan
                return if accur_is_nan === approx_is_nan
                    zero_return
                else
                    inf_return
                end
            end
            if isinf(approximate)
                return if isinf(accurate) && (signbit(accurate) == signbit(approximate))
                    zero_return
                else
                    inf_return
                end
            end
        end
        acc = if accurate isa Union{Float16, Float32}
            # widen for better accuracy when doing so does not impact performance too much
            widen(accurate)
        else
            accurate
        end
        abs(Float32((approximate - acc) / eps(approximate))::Float32)
    end
    function ulp_error(accurate, approximate, x::AbstractFloat)
        acc = accurate(x)
        app = approximate(x)
        ulp_error(acc, app)
    end
    function ulp_error(func::Func, x::AbstractFloat) where {Func}
        ulp_error(func ∘ BigFloat, func, x)
    end
    function ulp_error_maximum(func::Func, iterator) where {Func}
        maximum(Base.Fix1(ulp_error, func), iterator)
    end
end