pub trait BitField {
// Required methods
fn load_le<I>(&self) -> I
where I: Integral;
fn load_be<I>(&self) -> I
where I: Integral;
fn store_le<I>(&mut self, value: I)
where I: Integral;
fn store_be<I>(&mut self, value: I)
where I: Integral;
// Provided methods
fn load<I>(&self) -> I
where I: Integral { ... }
fn store<I>(&mut self, value: I)
where I: Integral { ... }
}
Expand description
C-Style Bit-Field Access
This trait describes data transfer between a BitSlice
region and an ordinary
integer. It is not intended for use by any other types than the data structures
in this crate.
The methods in this trait always operate on the bitslice.len()
least
significant bits of an integer, and ignore any remaining high bits. When
loading, any excess high bits not copied out of a bit-slice are cleared to zero.
Usage
The trait methods all panic if called on a bit-slice that is wider than the
integer type being transferred. As such, the first step is generally to subslice
a larger data structure into exactly the region used for storage, with
bits[start .. end]
. Then, call the desired method on the narrowed bit-slice.
Target-Specific Behavior
If you do not care about the details of the memory layout of stored values, you
can use the .load()
and .store()
unadorned methods. These each forward
to their _le
variant on little-endian targets, and their _be
variant on
big-endian. These will provide a reasonable default behavior, but do not
guarantee a stable memory layout, and their buffers are not suitable for
de/serialization.
If you require a stable memory layout, you will need to choose a BitSlice
with a fixed O: BitOrder
type parameter (not LocalBits
), and use a fixed
method suffix (_le
or _be
). You should probably also use u8
as your
T: BitStore
parameter, in order to avoid any byte-ordering issues. bitvec
never interferes with processor concepts of wide-integer layout, and always
relies on the target machine’s behavior for this work.
Element- and Bit- Ordering Combinations
Remember: the _le
and _be
method suffixes are completely independent of the
Lsb0
and Msb0
types! _le
and _be
refer to the order in which successive
memory elements are considered to gain numerical significance, while BitOrder
refers only to the order of successive bits in one memory element.
The BitField
and BitOrder
traits are not related.
When a load or store operation is contained in only one memory element, then the
_le
and _be
methods have the same behavior: they exchange an integer value
with the segment of the element that its governing BitSlice
considers live.
Only when a BitSlice
covers multiple elements does the distinction come into
play.
The _le
methods consider numerical significance to start low and increase with
increasing memory address, while the _be
methods consider numerical
significance to start high and decrease with increasing memory address. This
distinction affects the order in which memory elements are used to load or store
segments of the exchanged integer value.
Each trait method has detailed visual diagrams in its documentation.
Additionally, each implementation’s documentation has diagrams that show what
the governed bit-sections of elements are! Be sure to check each, or to run the
demonstration with cargo run --example bitfield
.
Bitfield Value Types
When interacting with a bit-slice as a C-style bitfield, it can only store the
signed or unsigned integer types. No other type is permitted, as the
implementation relies on the 2’s-complement significance behavior of processor
integers. Record types and floating-point numbers do not have this property, and
thus have no sensible default protocol for truncation and un/marshalling that
bitvec
can use.
If you have such a protocol, you may implement it yourself by providing a de/serialization transform between your type and the integers. For instance, a numerically-correct protocol to store floating-point numbers in bitfields might look like this:
use bitvec::mem::bits_of;
use funty::Floating;
fn to_storage<F>(num: F, width: usize) -> F::Raw
where F: Floating {
num.to_bits() >> (bits_of::<F>() - width)
}
fn from_storage<F>(val: F::Raw, width: usize) -> F
where F: Floating {
F::from_bits(val << (bits_of::<F>() - width))
}
This implements truncation in the least-significant bits, where floating-point numbers store disposable bits in the mantissa, rather than in the most-significant bits which contain the sign, exponent, and most significant portion of the mantissa.
Required Methods§
sourcefn load_le<I>(&self) -> Iwhere
I: Integral,
fn load_le<I>(&self) -> Iwhere I: Integral,
Little-Endian Integer Loading
This method loads an integer value from a bit-slice, using little-endian
significance ordering when the bit-slice spans more than one T
element in
memory.
Little-endian significance ordering means that if a bit-slice occupies an array
[A, B, C]
, then the bits stored in A
are considered to contain the least
significant segment of the loaded integer, then B
contains the middle segment,
and then C
contains the most significant segment.
The segments are combined in order, that is, as the raw bit-pattern
0b<padding><C><B><A>
. If the destination type is signed, the loaded value is
sign-extended according to the most-significant bit in the C
segment.
It is important to note that the O: BitOrder
parameter of the bit-slice from
which the value is loaded does not affect the bit-pattern of the stored
segments. They are always stored exactly as they exist in an ordinary integer.
The ordering parameter only affects which bits in an element are available for
storage.
Type Parameters
I
: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
&self
: A bit-slice region whose length is in the range1 ..= I::BITS
.
Returns
The contents of the bit-slice, interpreted as an integer.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
Let us consider an i32
value stored in 24 bits of a BitSlice<u8, Msb0>
:
use bitvec::prelude::*;
let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Msb0>();
let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_le::<i32>(integer);
let loaded = bits[4 .. 28].load_le::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);
Observe that, because the lowest 24 bits began with the pattern 0b1101…
, the
value was considered to be negative when interpreted as an i24
and was
sign-extended through the highest byte.
Let us now look at the memory representation of this value:
assert_eq!(raw, [
0b0000_1100,
// dead 0xC
0b0110_0011,
// 0x6 0x3
0b0100_1001,
// 0x4 0x9
0b1011_0000,
// 0xB dead
]);
Notice how while the Msb0
bit-ordering means that indexing within the
bit-slice proceeds left-to-right in each element, and the bit-patterns in each
element proceed left-to-right in the aggregate and the decomposed literals, the
ordering of the elements is reversed from how the literal was written.
In the sequence B496
, B
is the most significant, and so it gets placed
highest in memory. 49
fits in one byte, and is stored directly as written.
Lastly, 6
is the least significant nibble of the four, and is placed lowest
in memory.
Now let’s look at the way different BitOrder
parameters interpret the
placement of bit indices within memory:
use bitvec::prelude::*;
let raw = [
// Bit index 14 ←
// Lsb0: ─┤
0b0100_0000_0000_0011u16,
// Msb0: ├─
// → 14
// Bit index ← 19 16
// Lsb0: ├──┤
0b0001_0000_0000_1110u16,
// Msb0: ├──┤
// 16 19 →
];
assert_eq!(
raw.view_bits::<Lsb0>()
[14 .. 20]
.load_le::<u8>(),
0b00_1110_01,
);
assert_eq!(
raw.view_bits::<Msb0>()
[14 .. 20]
.load_le::<u8>(),
0b00_0001_11,
);
Notice how the bit-orderings change which parts of the memory are loaded, but
in both cases the segment in raw[0]
is less significant than the segment in
raw[1]
, and the ordering of bits within each segment are unaffected by the
bit-ordering.
Notes
Be sure to see the documentation for
<BitSlice<_, Lsb0> as BitField>::load_le
and
<BitSlice<_, Msb0> as Bitfield>::load_le
for more detailed information
on the memory views!
You can view the mask of all storage regions of a bit-slice by using its
.domain()
method to view the breakdown of its memory region, then print the
.mask()
of any PartialElement
the domain contains. Whole elements are
always used in their entirety. You should use the domain
module’s types
whenever you are uncertain of the exact locations in memory that a particular
bit-slice governs.
sourcefn load_be<I>(&self) -> Iwhere
I: Integral,
fn load_be<I>(&self) -> Iwhere I: Integral,
Big-Endian Integer Loading
This method loads an integer value from a bit-slice, using big-endian
significance ordering when the bit-slice spans more than one T
element in
memory.
Big-endian significance ordering means that if a bit-slice occupies an array
[A, B, C]
, then the bits stored in A
are considered to be the most
significant segment of the loaded integer, then B
contains the middle segment,
then C
contains the least significant segment.
The segments are combined in order, that is, as the raw bit-pattern
0b<padding><A><B><C>
. If the destination type is signed, the loaded value is
sign-extended according to the most-significant bit in the A
segment.
It is important to note that the O: BitOrder
parameter of the bit-slice from
which the value is loaded does not affect the bit-pattern of the stored
segments. They are always stored exactly as they exist in an ordinary integer.
The ordering parameter only affects which bits in an element are available for
storage.
Type Parameters
I
: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
&self
: A bit-slice region whose length is in the range1 ..= I::BITS
.
Returns
The contents of the bit-slice, interpreted as an integer.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
Let us consider an i32
value stored in 24 bits of a BitSlice<u8, Lsb0>
:
use bitvec::prelude::*;
let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Lsb0>();
let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_be::<i32>(integer);
let loaded = bits[4 .. 28].load_be::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);
Observe that, because the lowest 24 bits began with the pattern 0b1101…
, the
value was considered to be negative when interpreted as an i24
and was
sign-extended through the highest byte.
Let us now look at the memory representation of this value:
assert_eq!(raw, [
0b1011_0000,
// 0xB dead
0b0100_1001,
// 0x4 0x9
0b0110_0011,
// 0x6 0x3
0b0000_1100,
// dead 0xC
]);
Notice how while the Lsb0
bit-ordering means that indexing within the
bit-slice proceeds right-to-left in each element, the actual bit-patterns stored
in memory are not affected. Element [0]
is more numerically significant than
element [1]
, but bit [4]
is not more numerically significant than bit [5]
.
In the sequence B496
, B
is the most significant, and so it gets placed
lowest in memory. 49
fits in one byte, and is stored directly as written.
Lastly, 6
is the least significant nibble of the four, and is placed highest
in memory.
Now let’s look at the way different BitOrder
parameters interpret the
placement of bit indices within memory:
use bitvec::prelude::*;
let raw = [
// Bit index 14 ←
// Lsb0: ─┤
0b0100_0000_0000_0011u16,
// Msb0: ├─
// → 14
// Bit index ← 19 16
// Lsb0: ├──┤
0b0001_0000_0000_1110u16,
// Msb0: ├──┤
// 16 19 →
];
assert_eq!(
raw.view_bits::<Lsb0>()
[14 .. 20]
.load_be::<u8>(),
0b00_01_1110,
);
assert_eq!(
raw.view_bits::<Msb0>()
[14 .. 20]
.load_be::<u8>(),
0b00_11_0001,
);
Notice how the bit-orderings change which parts of the memory are loaded, but
in both cases the segment in raw[0]
is more significant than the segment in
raw[1]
, and the ordering of bits within each segment are unaffected by the
bit-ordering.
Notes
Be sure to see the documentation for
<BitSlice<_, Lsb0> as BitField>::load_be
and
<BitSlice<_, Msb0> as Bitfield>::load_be
for more detailed information
on the memory views!
You can view the mask of all storage regions of a bit-slice by using its
.domain()
method to view the breakdown of its memory region, then print the
.mask()
of any PartialElement
the domain contains. Whole elements are
always used in their entirety. You should use the domain
module’s types
whenever you are uncertain of the exact locations in memory that a particular
bit-slice governs.
sourcefn store_le<I>(&mut self, value: I)where
I: Integral,
fn store_le<I>(&mut self, value: I)where I: Integral,
Little-Endian Integer Storing
This method stores an integer value into a bit-slice, using little-endian
significance ordering when the bit-slice spans more than one T
element in
memory.
Little-endian significance ordering means that if a bit-slice occupies an array
[A, B, C]
, then the bits stored in A
are considered to contain the least
significant segment of the stored integer, then B
contains the middle segment,
and then C
contains the most significant segment.
An integer is broken into segments in order, that is, the raw bit-pattern is
fractured into 0b<padding><C><B><A>
. High bits beyond the length of the
bit-slice into which the integer is stored are truncated.
It is important to note that the O: BitOrder
parameter of the bit-slice into
which the value is stored does not affect the bit-pattern of the stored
segments. They are always stored exactly as they exist in an ordinary integer.
The ordering parameter only affects which bits in an element are available for
storage.
Type Parameters
I
: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
&mut self
: A bit-slice region whose length is in the range1 ..= I::BITS
.value
: An integer value whoseself.len()
least numerically significant bits will be written intoself
.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
Let us consider an i32
value stored in 24 bits of a BitSlice<u8, Msb0>
:
use bitvec::prelude::*;
let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Msb0>();
let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_le::<i32>(integer);
let loaded = bits[4 .. 28].load_le::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);
Observe that, because the lowest 24 bits began with the pattern 0b1101…
, the
value was considered to be negative when interpreted as an i24
and was
sign-extended through the highest byte.
Let us now look at the memory representation of this value:
assert_eq!(raw, [
0b0000_1100,
// dead 0xC
0b0110_0011,
// 0x6 0x3
0b0100_1001,
// 0x4 0x9
0b1011_0000,
// 0xB dead
]);
Notice how while the Msb0
bit-ordering means that indexing within the
bit-slice proceeds left-to-right in each element, and the bit-patterns in each
element proceed left-to-right in the aggregate and the decomposed literals, the
ordering of the elements is reversed from how the literal was written.
In the sequence B496
, B
is the most significant, and so it gets placed
highest in memory. 49
fits in one byte, and is stored directly as written.
Lastly, 6
is the least significant nibble of the four, and is placed lowest
in memory.
Now let’s look at the way different BitOrder
parameters interpret the
placement of bit indices within memory:
use bitvec::prelude::*;
let raw = [
// Bit index 14 ←
// Lsb0: ─┤
0b0100_0000_0000_0011u16,
// Msb0: ├─
// → 14
// Bit index ← 19 16
// Lsb0: ├──┤
0b0001_0000_0000_1110u16,
// Msb0: ├──┤
// 16 19 →
];
assert_eq!(
raw.view_bits::<Lsb0>()
[14 .. 20]
.load_le::<u8>(),
0b00_1110_01,
);
assert_eq!(
raw.view_bits::<Msb0>()
[14 .. 20]
.load_le::<u8>(),
0b00_0001_11,
);
Notice how the bit-orderings change which parts of the memory are loaded, but
in both cases the segment in raw[0]
is less significant than the segment in
raw[1]
, and the ordering of bits within each segment are unaffected by the
bit-ordering.
Notes
Be sure to see the documentation for
<BitSlice<_, Lsb0> as BitField>::store_le
and
<BitSlice<_, Msb0> as Bitfield>::store_le
for more detailed information
on the memory views!
You can view the mask of all storage regions of a bit-slice by using its
.domain()
method to view the breakdown of its memory region, then print the
.mask()
of any PartialElement
the domain contains. Whole elements are
always used in their entirety. You should use the domain
module’s types
whenever you are uncertain of the exact locations in memory that a particular
bit-slice governs.
sourcefn store_be<I>(&mut self, value: I)where
I: Integral,
fn store_be<I>(&mut self, value: I)where I: Integral,
Big-Endian Integer Storing
This method stores an integer value into a bit-slice, using big-endian
significance ordering when the bit-slice spans more than one T
element in
memory.
Big-endian significance ordering means that if a bit-slice occupies an array
[A, B, C]
, then the bits stored in A
are considered to contain the most
significant segment of the stored integer, then B
contains the middle segment,
and then C
contains the least significant segment.
An integer is broken into segments in order, that is, the raw bit-pattern is
fractured into 0b<padding><A><B><C>
. High bits beyond the length of the
bit-slice into which the integer is stored are truncated.
It is important to note that the O: BitOrder
parameter of the bit-slice into
which the value is stored does not affect the bit-pattern of the stored
segments. They are always stored exactly as they exist in an ordinary integer.
The ordering parameter only affects which bits in an element are available for
storage.
Type Parameters
I
: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
&mut self
: A bit-slice region whose length is in the range1 ..= I::BITS
.value
: An integer value whoseself.len()
least numerically significant bits will be written intoself
.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
Let us consider an i32
value stored in 24 bits of a BitSlice<u8, Lsb0>
:
use bitvec::prelude::*;
let mut raw = [0u8; 4];
let bits = raw.view_bits_mut::<Lsb0>();
let integer = 0x00__B4_96_3Cu32 as i32;
bits[4 .. 28].store_be::<i32>(integer);
let loaded = bits[4 .. 28].load_be::<i32>();
assert_eq!(loaded, 0xFF__B4_96_3Cu32 as i32);
Observe that, because the lowest 24 bits began with the pattern 0b1101…
, the
value was considered to be negative when interpreted as an i24
and was
sign-extended through the highest byte.
Let us now look at the memory representation of this value:
assert_eq!(raw, [
0b1011_0000,
// 0xB dead
0b0100_1001,
// 0x4 0x9
0b0110_0011,
// 0x6 0x3
0b0000_1100,
// dead 0xC
]);
Notice how while the Lsb0
bit-ordering means that indexing within the
bit-slice proceeds right-to-left in each element, the actual bit-patterns stored
in memory are not affected. Element [0]
is more numerically significant than
element [1]
, but bit [4]
is not more numerically significant than bit [5]
.
In the sequence B496
, B
is the most significant, and so it gets placed
lowest in memory. 49
fits in one byte, and is stored directly as written.
Lastly, 6
is the least significant nibble of the four, and is placed highest
in memory.
Now let’s look at the way different BitOrder
parameters interpret the
placement of bit indices within memory:
use bitvec::prelude::*;
let raw = [
// Bit index 14 ←
// Lsb0: ─┤
0b0100_0000_0000_0011u16,
// Msb0: ├─
// → 14
// Bit index ← 19 16
// Lsb0: ├──┤
0b0001_0000_0000_1110u16,
// Msb0: ├──┤
// 16 19 →
];
assert_eq!(
raw.view_bits::<Lsb0>()
[14 .. 20]
.load_be::<u8>(),
0b00_01_1110,
);
assert_eq!(
raw.view_bits::<Msb0>()
[14 .. 20]
.load_be::<u8>(),
0b00_11_0001,
);
Notice how the bit-orderings change which parts of the memory are loaded, but
in both cases the segment in raw[0]
is more significant than the segment in
raw[1]
, and the ordering of bits within each segment are unaffected by the
bit-ordering.
Notes
Be sure to see the documentation for
<BitSlice<_, Lsb0> as BitField>::store_be
and
<BitSlice<_, Msb0> as Bitfield>::store_be
for more detailed information
on the memory views!
You can view the mask of all storage regions of a bit-slice by using its
.domain()
method to view the breakdown of its memory region, then print the
.mask()
of any PartialElement
the domain contains. Whole elements are
always used in their entirety. You should use the domain
module’s types
whenever you are uncertain of the exact locations in memory that a particular
bit-slice governs.
Provided Methods§
sourcefn load<I>(&self) -> Iwhere
I: Integral,
fn load<I>(&self) -> Iwhere I: Integral,
Integer Loading
This method reads the contents of a bit-slice region as an integer. The region
may be shorter than the destination integer type, in which case the loaded value
will be zero-extended (when I: Unsigned
) or sign-extended from the most
significant loaded bit (when I: Signed
).
The region may not be zero bits, nor wider than the destination type. Attempting
to load a u32
from a bit-slice of length 33 will panic the program.
Operation and Endianness Handling
Each element in the bit-slice contains a segment of the value to be loaded. If the bit-slice contains more than one element, then the numerical significance of each loaded segment is interpreted according to the target’s endianness:
- little-endian targets consider each
T
element to have increasing numerical significance, starting with the least-significant segment at the low address and ending with the most-significant segment at the high address. - big-endian targets consider each
T
element to have decreasing numerical significance, starting with the most-significant segment at the high address and ending with the least-significant segment at the low address.
See the documentation for .load_le()
and .load_be()
for more detail on
what this means for how the in-memory representation of bit-slices translates to
loaded values.
You must always use the loading method that exactly corresponds to the storing
method previously used to insert data into the bit-slice: same suffix on the
method name (none, _le
, _be
) and same integer type. bitvec
is not required
to, and will not, guarantee round-trip consistency if you change any of these
parameters.
Type Parameters
I
: The integer type being loaded. This can be any of the signed or unsigned integers.
Parameters
&self
: A bit-slice region whose length is in the range1 ..= I::BITS
.
Returns
The contents of the bit-slice, interpreted as an integer.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
This method is inherently non-portable, and changes behavior depending on the
target characteristics. If your target is little-endian, see .load_le()
; if
your target is big-endian, see .load_be()
.
sourcefn store<I>(&mut self, value: I)where
I: Integral,
fn store<I>(&mut self, value: I)where I: Integral,
Integer Storing
This method writes an integer into the contents of a bit-slice region. The region may be shorter than the source integer type, in which case the stored value will be truncated. On load, it may be zero-extended (unsigned destination) or sign-extended from the most significant stored bit (signed destination).
The region may not be zero bits, nor wider than the source type. Attempting
to store a u32
into a bit-slice of length 33 will panic the program.
Operation and Endianness Handling
The value to be stored is broken into segments according to the elements of the bit-slice receiving it. If the bit-slice contains more than one element, then the numerical significance of each segment routes to a storage element according to the target’s endianness:
- little-endian targets consider each
T
element to have increasing numerical significance, starting with the least-significant segment at the low address and ending with the most-significant segment at the high address. - big-endian targets consider each
T
element to have decreasing numerical significance, starting with the most-significant segment at the high address and ending with the least-significant segment at the low address.
See the documentation for .store_le()
and .store_be()
for more detail on
what this means for how the in-memory representation of bit-slices translates to
stored values.
You must always use the loading method that exactly corresponds to the storing
method previously used to insert data into the bit-slice: same suffix on the
method name (none, _le
, _be
) and same integer type. bitvec
is not required
to, and will not, guarantee round-trip consistency if you change any of these
parameters.
Type Parameters
I
: The integer type being stored. This can be any of the signed or unsigned integers.
Parameters
&self
: A bit-slice region whose length is in the range1 ..= I::BITS
.value
: An integer value whoseself.len()
least numerically significant bits will be written intoself
.
Panics
This panics if self.len()
is 0, or greater than I::BITS
.
Examples
This method is inherently non-portable, and changes behavior depending on the
target characteristics. If your target is little-endian, see .store_le()
; if
your target is big-endian, see .store_be()
.
Object Safety§
Implementors§
impl<A, O> BitField for BitArray<A, O>where O: BitOrder, A: BitViewSized, BitSlice<A::Store, O>: BitField,
Bit-Array Implementation of BitField
The BitArray
implementation is only ever called when the entire bit-array is
available for use, which means it can skip the bit-slice memory detection and
instead use the underlying storage elements directly.
The implementation still performs the segmentation for each element contained in the array, in order to maintain value consistency so that viewing the array as a bit-slice is still able to correctly interact with data contained in it.
impl<T> BitField for BitSlice<T, Lsb0>where T: BitStore,
Lsb0
Bit-Field Behavior
BitField
has no requirements about the in-memory representation or layout of
stored integers within a bit-slice, only that round-tripping an integer through
a store and a load of the same element suffix on the same bit-slice is
idempotent (with respect to sign truncation).
Lsb0
provides a contiguous translation from bit-index to real memory: for any
given bit index n
and its position P(n)
, P(n + 1)
is P(n) + 1
. This
allows it to provide batched behavior: since the section of contiguous indices
used within an element translates to a section of contiguous bits in real
memory, the transaction is always a single shift/mask operation.
Each implemented method contains documentation and examples showing exactly how the abstract integer space is mapped to real memory.
impl<T> BitField for BitSlice<T, Msb0>where T: BitStore,
Msb0
Bit-Field Behavior
BitField
has no requirements about the in-memory representation or layout of
stored integers within a bit-slice, only that round-tripping an integer through
a store and a load of the same element suffix on the same bit-slice is
idempotent (with respect to sign truncation).
Msb0
provides a contiguous translation from bit-index to real memory: for any
given bit index n
and its position P(n)
, P(n + 1)
is P(n) - 1
. This
allows it to provide batched behavior: since the section of contiguous indices
used within an element translates to a section of contiguous bits in real
memory, the transaction is always a single shift-mask operation.
Each implemented method contains documentation and examples showing exactly how the abstract integer space is mapped to real memory.
Notes
In particular, note that while Msb0
indexes bits from the most significant
down to the least, and integers index from the least up to the most, this
does not reörder any bits of the integer value! This ordering only finds a
region in real memory; it does not affect the partial-integer contents stored
in that region.