Range checking / bounds (of a variable) checking

Hello,

In my old memories, I recall that Ada (programming language) provide a concept of “range” for checking that a numerical value stored in a variable is always between a lower bound and an upper bound.
This kind of check, is done at run time.
I wonder if this kind of mechanism currently exist in Julia.
For example I would like to say that I store an unsigned integer 8 but that upper bound can’t be upper than 10 and lower bound must be greater than or equal to 1.
I only find information about bound checking for array indices in doc.
Any idea?

Kind regards

3 Likes

Make your own type wrapping the number, implement the methods and provide the checking in an inner constructor.

julia> struct RangeNumber{T<:Number, R}
           v::T

           function RangeNumber(v::Number, r::UnitRange)
               v in r || error("$v not in $r")
               return new{typeof(v), r}(v)
           end
       end

julia> Base.:+(a::RangeNumber{T, R}, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a.v+b.v, R)

julia> Base.:+(a::Number, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a+b.v, R)

julia> Base.:+(a::RangeNumber{T, R}, b::Number) where {T, R} = RangeNumber(a.v+b, R)

julia> a = RangeNumber(3, 1:7)
RangeNumber{Int64,1:7}(3)

julia> a+a
RangeNumber{Int64,1:7}(6)

julia> a+a+a
ERROR: 9 not in 1:7
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] RangeNumber(::Int64, ::UnitRange{Int64}) at ./REPL[1]:5
 [3] + at ./REPL[3]:1 [inlined]
 [4] +(::RangeNumber{Int64,1:7}, ::RangeNumber{Int64,1:7}, ::RangeNumber{Int64,1:7}) at ./operators.jl:502
 [5] top-level scope at none:0

julia> a+3
RangeNumber{Int64,1:7}(6)

julia> a+5
ERROR: 8 not in 1:7
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] RangeNumber(::Int64, ::UnitRange{Int64}) at ./REPL[1]:5
 [3] +(::RangeNumber{Int64,1:7}, ::Int64) at ./REPL[5]:1
 [4] top-level scope at none:0

etc

4 Likes

Very interesting althought I would prefer to also be able to call

RangeNumber{Int64,1:7}(3)

So I can store the “restriction” (ie RangeNumber{Int64,1:7}) and use it later.

Just add the constructor

julia> RangeNumber{T, R}(n) where {T, R} = RangeNumber(convert(T, n), R)

julia> RangeNumber{Int64,1:7}(3)
RangeNumber{Int64,1:7}(3)

julia> RangeNumber{Int64,1:7}(8)
ERROR: 8 not in 1:7
2 Likes

Great! Thanks @kristoffer.carlsson

I’m still a bit stuck up.

I would like to be able to store a restriction (range checking) but also the lack of range checking.

I did

abstract type AbstractRangeNumber end

Number(rn::AbstractRangeNumber) = rn.v


struct NoRangeNumber{T<:Number} <: AbstractRangeNumber
    v::T
    function NoRangeNumber(v::Number)
        return new{typeof(v)}(v)
    end
end


"""
    RangeNumber(v, r)

# Example
a = RangeNumber(3, 1:7)
"""
struct RangeNumber{T<:Number, R} <: AbstractRangeNumber
    v::T

    function RangeNumber(v::Number, r::UnitRange)
        v in r || error("$v not in $r")
        return new{typeof(v), r}(v)
    end
end

RangeNumber{T, R}(n) where {T, R} = RangeNumber(n, R)

Base.:+(a::RangeNumber{T, R}, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a.v+b.v, R)
Base.:+(a::Number, b::RangeNumber{T, R}) where {T, R} = RangeNumber(a+b.v, R)
Base.:+(a::RangeNumber{T, R}, b::Number) where {T, R} = RangeNumber(a.v+b, R)

Base.:(==)(a::AbstractRangeNumber{T, R}, b::AbstractRangeNumber{T, R}) where {T, R} = a.v == b.v
Base.:(==)(a::Number, b::AbstractRangeNumber{T, R}) where {T, R} = a == b.v
Base.:(==)(a::AbstractRangeNumber{T, R}, b::Number) where {T, R} = a.v == b

and tests:

using Nextion: RangeNumber, NoRangeNumber
using Test


@testset "RangeNumber" begin
    @testset "construct / isequal" begin
        a = RangeNumber(3, 1:7)
        @test a == 3
    end

    @testset "addition / overflow" begin
        a = RangeNumber(3, 1:7)
        @test a + a == 6 
        @test_throws ErrorException a + a + a == 9
    end

    @testset "other construct" begin
        a = RangeNumber{Int64, 1:7}(3)
        @test a == 3

        @test_throws ErrorException a = RangeNumber{Int64, 1:7}(8)
    end

    @testset "NoRangeNumber" begin
        a = NoRangeNumber(3)
        @test a == 3
    end

end

but it’s failling for odd reason

$ julia test/test_range_number.jl 
ERROR: LoadError: LoadError: TypeError: in Type{...} expression, expected UnionAll, got Type{Nextion.AbstractRangeNumber}
Stacktrace:
 [1] top-level scope at none:0
 [2] include at ./boot.jl:317 [inlined]
 [3] include_relative(::Module, ::String) at ./loading.jl:1038
 [4] include at ./sysimg.jl:29 [inlined]
 [5] include(::String) at /home/scls/.julia/dev/Nextion/src/Nextion.jl:37
 [6] top-level scope at none:0
 [7] include at ./boot.jl:317 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1038
 [9] include(::Module, ::String) at ./sysimg.jl:29
 [10] top-level scope at none:2
 [11] eval at ./boot.jl:319 [inlined]
 [12] eval(::Expr) at ./client.jl:399
 [13] top-level scope at ./none:3

The general idea is to be able to store either a restriction or a lack of restriction in numbers.