Why doesn't FixedPointNumbers.jl allow comparison with integers?

I am using FixedPointNumbers.jl to implement an approximation algorithm, and I have been running into the following issue:

julia> using FixedPointNumbers
julia> FP = Fixed{Int16, 4}
Q11f4 (alias for Fixed{Int16, 4})
julia> typemax(FP)
2047.94Q11f4
julia> 2099 > typemax(FP)
ERROR: ArgumentError: Q11f4 is a 16-bit type representing 65536 values from -2048.0 to 2047.94; cannot represent 2099

Would you consider this a bug, or is leaving comparisons undefined when number falls out of the type range the standard practice?

If throwing an error as above is the standard practice to prevent overflow issues, what should I do instead? Calling 2099 > Float64(typemax(FP)) feels like overkill.

FWIW, Julia’s builtin types do not have this behavior: 130 > typemax(Int8) is true.

Looks like there is no specialized method for comparison, so a generic method is attempting to promote the Int to the fixed-point number, and failing since the Int is too large.

An easy fix would be to add a method which first checks if the non fixed-point number is greater/less than the typemax/min of the fixed-point number before trying the promote and compare fallback. Something along the lines of:

function Base.:<(x::T, y::U) where {T<:Real, U<:FixedPoint}
    if x > round(T, typemax(y), RoundUp)
        return false
    elseif x < round(T, typemin(y), RoundDown)
        return true
    end
    _x, _y = promote(x,y)
    return <(_x.i, _y.i)
end
Base.:<(x::U, y::T) where {U<:Fixed, T<:Real} = !(y < x)
julia> FP = Fixed{Int16, 4}
Q11f4 (alias for Fixed{Int16, 4})

julia> typemin(FP), typemax(FP)
(-2048.0Q11f4, 2047.94Q11f4)

julia> 2099 > typemax(FP)
true

julia> 2040 > typemax(FP)
false

julia> -2099 < typemin(FP)
true

julia> -2040 < typemin(FP)
false
3 Likes

Nice! But I believe the promote_rule for Fixed is intentional:

@show 2048 < 2047.1Q11f4

throws

ERROR: LoadError: ArgumentError: Q11f4 is a 16-bit type representing 65536 values from -2048.0 to 2047.94; cannot represent 2048

with your method.

1 Like

Good catch! My suggestion doesn’t change promotion or promotion rules, just when promotion happens for this specific case. Looks like the out-of-range comparisons should be ≤ or ≥, since, e.g. for an Int, 2047.1 rounded up to 2048 is also (obviously) too big.

function Base.:<(x::T, y::U) where {T<:Real, U<:FixedPoint}
    if x >= round(T, typemax(y), RoundUp)
        return false
    elseif x <= round(T, typemin(y), RoundDown)
        return true
    end
    _x, _y = promote(x,y)
    return <(_x.i, _y.i)
end

Now:

julia> @show 2048 < 2047.1Q11f4
2048 < 2047.1Q11f4 = false
false
1 Like