Non-negative integers

I want to do some things with non-negative integers and use it in function signatures. Is this idiomatic?

# Concrete subtypes should have a `val` attribute.
abstract type NonNegativeInteger <: Integer end

struct NonNegativeInt{T} <: NonNegativeInteger

Base.:+(x::T, y::T) where T <: NonNegativeInteger = T(x.val + y.val)
Base.:*(x::T, y::T) where T <: NonNegativeInteger = T(x.val * y.val)
Base.:^(x::T, y::T) where T <: NonNegativeInteger = T(x.val ^ y.val)

a = NonNegativeInt(2)
b = NonNegativeInt(3)
(a + b, a * b, a ^ b)

Out of curiosity, why not using UInt* types?

I could probably use SaferIntegers.UInt. So maybe nonnegative real/rational/float would be a better example to ask the question with.

There isn’t such a type for floating-point numbers; you could define one, of course, but this is something that’s probably better done with a runtime argument check:

function f(x::Real)
     x ≥ 0 || throw(DomainError("negative x=$x not allowed"))

Note that if x is an Unsigned type (like UInt, and also things like Rational{UInt}), the compiler will know that x ≥ 0 is always true and optimize out the check.

1 Like

Is >= 0 a special case, or can I somehow make my own compiler-optimized predicates?

≥ 0 is not a special case, in the sense that the compiler (LLVM) does constant folding and dead-code elimination on any predicate that is inlined (any sufficiently short function). Whether this results in an elided check depends on what you are doing and the limits of LLVM.

1 Like