Constructors for Primitive types

My goal is to have my own primitive integer type, for which I need to specialize dispatch.

primitive type Basis{V,G} <: Unsigned 16 end

How do I create a constructor for it? The documentation does not explain how.

julia> Basis{4,3}(0x0007)
ERROR: MethodError: no method matching Basis{4,3}(::UInt16)
Closest candidates are:
  Basis{4,3}(::T<:Number) where T<:Number at boot.jl:725
  Basis{4,3}(::Float16) where T<:Integer at float.jl:71
  Basis{4,3}(::Complex) where T<:Real at complex.jl:37
  ...

Try this:

primitive type Basis{V,G} <: Unsigned 16 end

Basis{V,G}(x::UInt16) where {V,G} = reinterpret(Basis{V,G}, x)

UInt16(x::Basis) = reinterpret(UInt16, x)

Base.show(io::IO, x::Basis) = print(io, UInt16(x))

Basis{4,3}(0x0007)
4 Likes

This question comes up very often; please consider contributing @bennedich’s answer to the docs as a PR.

2 Likes

For now I created a repository which holds my basic concept for a primitive bit array…

However, the reinterpret thing will only work if the number of bits matches an existing type, so it does not work in general for arbitrary number of bits.

The question remains open for bit sizes such as 48 for example.

Nice! You know about BitArray, right? It contains similar functionality, and generally has pretty good performance, e.g.:

julia> a = BitArray(bitrand(64));

julia> @btime a[2:63];
  63.278 ns (3 allocations: 160 bytes)

julia> b = PrimitiveBits64(a);

julia> @btime b[2:63];
  368.627 ns (2 allocations: 176 bytes)

One difference / disadvantage being that the minimum storage is 64 bits, so it’s not very space efficient for small types, and thus might not be what you need. Perhaps performance is not a concern for your implementation, but glancing over your code, I would propose the following trivial but impactful change:

-  d = 2 ^ (i-1)
+  d = 1 << (i-1)

I.e. to use shifting rather than exponentiation. Running the same test as above:

julia> @btime b[2:63];
  106.148 ns (2 allocations: 176 bytes)
1 Like

Thanks for the suggestion. When using smaller bit sizes, the performance is more comparable

julia> a = BitArray(rand(Bool,16));

julia> b = PrimitiveBits16(a);

julia> f(a,x) = a[x];

julia> @btime f($a,2:14);
  41.007 ns (2 allocations: 128 bytes)

julia> @btime f($b,2:14);
  46.182 ns (1 allocation: 96 bytes)

This type is more convenient than a BitArray if you need to convert to an integer more frequently.