# \[ANN\] EmulatedBitIntegers.jl

**URL:** <https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830>\
**Category:** Package Announcements\
**Created:** [June 28, 2026, 5:01am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830 "2026-06-28T05:01:02Z")\
**Posts on this page:** 18\
**Page:** 1

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 28, 2026, 5:01am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/1 "2026-06-28T05:01:02Z")

</div>

I am happy to announce that we open-sourced EmulatedBitIntegers.jl. The package allows defining arbitrary bit width integers like `UInt3` or `Int7` which behave mathematically as you would expect, by overflowing and underflowing like a native implementation would do.

Internally, the package stores the values in a storage type which is a regular (non-emulated) primitive bit integer like `UInt8`. By implementing the Julia `Base` methods, the user normally does not need to know this except for two things

- Storage space might be larger than expected, e.g. if you have a struct with two `UInt4` values it will be 2 bytes instead of a single byte. But see the [announcement for PackedStructs.jl](https://discourse.julialang.org/t/ann-packedstructs-jl/137831) how to avoid this.
- Using them can be a bit slower due to the overflow and underflow guarantee which needs additional instructions in general.

That being said the package is highly performance optimized and for all I know generates the same machine code as a future Julia-native LLVM-based approach will generate (at least with Julia 1.12 and 1.13; Julia 1.10 lacks some LLVM optimizations). With all the progresses in superscalar processor architectures we have seen in the last two decades, there is no performance difference between the emulated bit integers and the native bit integers more often than not.

Related Work

- [Simen Gaure added a pull request to `BitIntegers.jl`](https://github.com/rfourquet/BitIntegers.jl/pull/54) which implements support for arbitrary bit integers via `llvmcall`.

- Work is ongoing to support arbitrary bit integers directly in Julia, see e.g. [Julia #45486](https://github.com/JuliaLang/julia/issues/45486) and [Julia #61359](https://github.com/JuliaLang/julia/pull/61359). I would be glad if this work makes EmulatedBitIntegers.jl obsolete.

Thanks to the nice people in [JuliaData](https://github.com/JuliaData/), who kindly allowed me to move and maintain the code there.

---

<div class="post-metadata">

**Author:** ![johnh](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnh/32/3615_2.png) [@johnh](https://discourse.julialang.org/u/johnh)\
**Post date:** [June 28, 2026, 11:54am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/2 "2026-06-28T11:54:49Z")

</div>

Int 7 ??? Gaccckk… I hate odd numbers.

In fact my scheme for increasing LLM performance is a number system with no odd numbers

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 28, 2026, 12:50pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/3 "2026-06-28T12:50:00Z")

</div>

The combination EmulatedBitIntegers.jl + PackedStructs.jl seems somewhat related to the type [`PackedVector`](https://matthias314.github.io/SmallCollections.jl/stable/packedvector/) from SmallCollections.jl. (Disclaimer: I’m the author.) For example

```julia-auto
julia> v = PackedVector{UInt16,5,Int8}([1,9])
2-element PackedVector{UInt16, 5, Int8}:
 1
 9

```

defines a vector with 5-bit integers that are packed into an `UInt16` (hence with maximal length 3) and appear to the outside as `Int8`. Besides vector operations like

```julia-auto
julia> Int8(3) * v
2-element PackedVector{UInt16, 5, Int8}:
  3
 -5

```

(note the overflow) there are functions like `push`, `filter` or `findfirst`.

Maybe one can combine `PackedVector` with EmulatedBitIntegers.jl to declare an honest element type `Int5` instead of the fake `Int8`.

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [June 28, 2026, 2:50pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/4 "2026-06-28T14:50:58Z")

</div>

> [@PatrickHaecker](#):
>
> defining arbitrary bit width integers like `UInt3` or `Int7` which behave mathematically as you would expect, by overflowing and underflowing like a native implementation would do

Can you please give some examples where this is useful?

(I am not questioning that it is useful, I just want to learn new things. This is outside my field of expertise and I am being curious.)

---

<div class="post-metadata">

**Author:** ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)\
**Post date:** [June 28, 2026, 2:57pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/5 "2026-06-28T14:57:38Z")

</div>

> [@Tamas\_Papp](#):
>
> Can you please give some examples where this is useful?

Although I don’t think that I’ve seen Int7 in particular, these kinds of “weird” integers are somewhat common when dealing with certain protocols or with data-aware serialization. If you know your data distribution is heavily skewed, you can use small integers as codewords for the most common information, thereby saving bandwidth. It’s of course also common when interfacing with certain hardware, since they sometimes only have limited precision for their output registers.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 29, 2026, 7:10am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/6 "2026-06-29T07:10:01Z")

</div>

> [@matthias314](#):
>
> Maybe one can combine `PackedVector` with [EmulatedBitIntegers.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/EmulatedBitIntegers) to declare an honest element type `Int5` instead of the fake `Int8`.

Thanks, @matthias314, this is an interesting idea, as a `PackedVector` is the logical pendant to a `PackedStruct`. I haven’t looked into the code of `PackedStructs`, but there seems at least to be an overly restrictive type check:

```julia-repl
julia> using SmallCollections, EmulatedBitIntegers

julia> @emulate Int2

julia> (1, -1, 0, 1) |> PackedVector{UInt8, 2, Int2}
ERROR: TypeError: in PackedVector, in T, expected T<:Union{Bool, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}, got Type{Int2}
Stacktrace:
 [1] top-level scope
   @ REPL[22]:1

```

I don’t know how the interface for a generic `PackedStruct` would look like. If you don’t want to restrict yourself to small collections, I think it would at least not contain the first parameter. It should also be able to auto-group, which `PackedStruct` from SmallCollections.jl already does, If I understood this correctly.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 29, 2026, 7:16am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/7 "2026-06-29T07:16:14Z")

</div>

> [@Tamas\_Papp](#):
>
> Can you please give some examples where this is useful?

In addition to @Sukera’s answer, I’d say these kind of types can be useful whenever you consider using something else than `Int`, so e.g. when you

- want to make use of the modulo behavior, e.g. the index of a ring buffer
- want to limit the allowed values with the type instead of with additional constructor constraint in immutable structs and/or with additional `setindex!` constraints in mutable structs
- want to accelerate your code by optimizing caching together with e.g. `PackedStruct`s
- want to ensure correctness with the type system without exploding the sizes, by e.g. widen by a single bit when doing addition instead of doubling the bit size

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 29, 2026, 12:59pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/8 "2026-06-29T12:59:30Z")

</div>

I’ve added support for EmulatedBitIntegers.jl to `PackedVector`. I haven’t done much testing yet, but it appears to work in principle:

```julia-auto
julia> using SmallCollections, EmulatedBitIntegers

julia> @emulate Int5 UInt5

julia> v = PackedVector{6, Int5}(3:5) # (at least) 6 elements of type Int5
3-element PackedVector{UInt32, 5, Int5}:
 3
 4
 5

julia> v[1], typeof(v[1])
(3, Int5)

```

To get this branch, say `add SmallCollections#emulated` in the package manager.

EDIT: One now also needs to define the unsigned version of every new integer type (`UInt5` above).

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 29, 2026, 2:55pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/9 "2026-06-29T14:55:59Z")

</div>

That sounds totally great!

However, I seem to be doing something wrong. Any idea?

```julia-repl
julia> using SmallCollections, EmulatedBitIntegers

julia> @emulate Int5

julia> PackedVector{UInt16, 3, Int5}(3:5)
ERROR: TypeError: in PackedVector, in T, expected T<:Union{Bool, Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}, got Type{Int5}
Stacktrace:
 [1] top-level scope
   @ REPL[2]:1

julia> PackedVector{6, Int5}(3:5)
ERROR: TypeError: in PackedVector, in U, expected U<:Unsigned, got a value of type Int64
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

(@v1.13) pkg> status SmallCollections
Status `~/.julia/environments/v1.13/Project.toml`
  [2b935e18] SmallCollections v0.6.3-DEV `https://github.com/matthias314/SmallCollections.jl.git#emulated`

```

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 29, 2026, 5:14pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/10 "2026-06-29T17:14:56Z")

</div>

@PatrickHaecker Sorry, forgot to commit. Now it should work.

Caveat: I’ve tried several machines, and on one I get an `InexactError`. No idea why. On other machines it works.

EDIT: This seems to be an [issue with bit truncation](https://github.com/JuliaData/EmulatedBitIntegers.jl/issues/7) in EmulatedBitIntegers.jl.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 30, 2026, 4:58am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/11 "2026-06-30T04:58:31Z")

</div>

Thanks for the report, @matthias314! I fixed the problem. It turned out, that here we could get even faster code, because we never need to throw and therefore even in the good case we can skip the branch.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 30, 2026, 5:38am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/12 "2026-06-30T05:38:30Z")

</div>

> [@matthias314](#):
>
> Sorry, forgot to commit. Now it should work.

Now it works. Thanks, @matthias314!

I probably do not understand the package well enough, but isn’t it at least by default quite wasteful for being a small collection? In this example 3 (practically 4) bytes would suffice, but 16 bytes are used:

```julia-repl
julia> v = PackedVector{3, Int5}(3:5)
3-element PackedVector{UInt16, 5, Int5}:
 3
 4
 5

julia> v |> about
3-element PackedVector{UInt16, 5, Int5} (<: AbstractVector{Int5} <: Any), occupies 16B.
 m::UInt16 8B 0001010010000011 0x1483
 n::Int64 8B 0000000000000000000000000000000000000000000000000000000000000011 3

 ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
       2B+6B 8B

```

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 30, 2026, 11:51am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/13 "2026-06-30T11:51:14Z")

</div>

> [@PatrickHaecker](#):
>
> In this example 3 (practically 4) bytes would suffice, but 16 bytes are used

You are right. The length should definitely not be stored as an `Int`. Thanks for catching this!

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [June 30, 2026, 1:02pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/14 "2026-06-30T13:02:27Z")

</div>

> [@matthias314](#):
>
> The length should definitely not be stored as an `Int`

I can imagine It should be stored in a type defined by a parameter, which might by default be an `Int`.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [August 24, 2026, 6:38am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/15 "2026-08-24T06:38:37Z")

</div>

The master version of SmallCollections.jl now stores the length of a `PackedVector` as a 2-byte integer.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [September 9, 2026, 3:08am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/16 "2026-09-09T03:08:38Z")

</div>

That looks awesome! Thanks, @matthias314. Sorry for being behind with processing all the progress from you.

Any reason why the length is not a type parameter of `PackedVector` with the constructors defaulting to `master`’s `Int16`? I don’t think that there are too many `Int32` use cases, but I definitely see `UInt8` use cases.

I also wondered whether you might be able to get rid of your type parameter `M` by adopting EmulatedBitIntegers’ `bits` interface. We could also extract that interface into a separate package to keep your dependencies smaller (and cleaner) until the `Base` interface is available in the Julia LTS version. Or wouldn’t this work because you want to be able to allow padding by `M > bits(T)`?

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [September 9, 2026, 11:32pm UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/17 "2026-09-09T23:32:45Z")

</div>

> [@PatrickHaecker](#):
>
> `UInt8` use cases

Yes, that would be sufficient for most (but not all) use cases. However, I wonder how much difference it would make. Unless the bit mask has only one byte, alignment rules prevent memory savings for smaller length types (without packed structs, that is).

Introducing an additional parameter for the length type would make `PackedVector{U,M,T}` an abstract type, which is somewhat inconvenient. More importantly, I’m thinking of storing the length inside the bit mask and not as a separate field. That would solve the alignment issue.

> get rid of your type parameter `M`

I agree. The current first step is to make the existing API work with EmulatedBitIntegers.jl. If the parameter `M` is removed, then one has no choice but to use `EmulatedInteger` types. There are still some open [issues](https://github.com/JuliaData/EmulatedBitIntegers.jl/issues) with them. I’d like to wait until they are sorted out before I make that switch.

---

<div class="post-metadata">

**Author:** ![PatrickHaecker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/patrickhaecker/32/222891_2.png) [@PatrickHaecker](https://discourse.julialang.org/u/PatrickHaecker)\
**Post date:** [September 10, 2026, 8:48am UTC](https://discourse.julialang.org/t/ann-emulatedbitintegers-jl/137830/18 "2026-09-10T08:48:27Z")

</div>

> However, I wonder how much difference it would make. Unless the bit mask has only one byte, alignment rules prevent memory savings for smaller length types (without packed structs, that is).

Ah, indeed, good point. The primary use cases I see are around a collection of small, fixed-sized (but not static) different-length collections, i.e. a ragged array. If the elements are 8 bits each, it would be unfortunate if the length increased the alignment to 16 bits. However, you are right that this would probably imply a clever collaboration of multiple packages, as it won’t work with the large bitmask.

> I’d like to wait until they are sorted out before I make that switch.

Thanks for that extra motivation. I think I addressed the open issues, but I’ve already identified some more, which I still need to look into in more detail.

But anyway, it’s not clear whether the `bits` interface belongs into EmulatedBitIntegers.jl or should be separate. On the one hand, every type can have a non-byte size, not only the primitive types EmulatedBitIngers.jl focuses on. On the other hand, basically all relevant types build on these primitive types (with maybe the exception of union tags and the like).  
We should probably clarify this question first, because extracting the interface in a separate package is easier if there is only a single user which needs to be coordinated.
