The D Programming Language

Bit-level manipulation facilities.

License
Boost License 1.0.
Authors
Walter Bright, Andrei Alexandrescu, Jonathan M Davis, Alex Rønne Petersen, Damian Ziemba
Source:
std/bitmanip.d

template  bitfields(T...)

Allows creating bit fields inside structs and classes.

Example:
struct A
{
    int a;
    mixin(bitfields!(
        uint, "x",    2,
        int,  "y",    3,
        uint, "z",    2,
        bool, "flag", 1));
}
A obj;
obj.x = 2;
obj.z = obj.x;


The example above creates a bitfield pack of eight bits, which fit in one ubyte. The  bitfields are allocated starting from the least significant bit, i.e. x occupies the two least significant bits of the  bitfields storage.

The sum of all bit lengths in one bitfield instantiation must be exactly 8, 16, 32, or 64. If padding is needed, just allocate one bitfield with an empty name.
Example:
struct A
{
    mixin(bitfields!(
        bool, "flag1",    1,
        bool, "flag2",    1,
        uint, "",         6));
}


The type of a bit field can be any integral type or enumerated type. The most efficient type to store in  bitfields is bool, followed by unsigned types, followed by signed types.

struct  FloatRep;

Allows manipulating the fraction, exponent, and sign parts of a float separately. The definition is:

struct FloatRep
{
    union
    {
        float value;
        mixin(bitfields!(
                  uint,  "fraction", 23,
                  ubyte, "exponent",  8,
                  bool,  "sign",      1));
    }
    enum uint bias = 127, fractionBits = 23, exponentBits = 8, signBits = 1;
}


struct  DoubleRep;

Allows manipulating the fraction, exponent, and sign parts of a double separately. The definition is:

struct DoubleRep
{
    union
    {
        double value;
        mixin(bitfields!(
                  ulong,   "fraction", 52,
                  ushort,  "exponent", 11,
                  bool,    "sign",      1));
    }
    enum uint bias = 1023, signBits = 1, fractionBits = 52, exponentBits = 11;
}


struct  BitArray;

An array of bits.


const @property size_t  dim();

Gets the amount of native words backing this BitArray.


const @property size_t  length();

Gets the amount of bits in the BitArray.


@property size_t  length(size_t newlen);

Sets the amount of bits in the BitArray.


const bool  opIndex(size_t i);

Gets the i'th bit in the BitArray.


bool  opIndexAssign(bool b, size_t i);

Sets the i'th bit in the BitArray.


const @property BitArray  dup();

Duplicates the BitArray and its contents.


int  opApply(scope int delegate(ref bool) dg);
const int  opApply(scope int delegate(bool) dg);
int  opApply(scope int delegate(ref size_t, ref bool) dg);
const int  opApply(scope int delegate(size_t, bool) dg);

Support for foreach loops for BitArray.


@property BitArray  reverse();

Reverses the bits of the BitArray.


@property BitArray  sort();

Sorts the BitArray's elements.


const bool  opEquals(ref const BitArray a2);

Support for operators == and != for BitArray.


const int  opCmp(BitArray a2);

Supports comparison operators for BitArray.


const pure nothrow size_t  toHash();

Support for hashing for BitArray.


void  init(bool[] ba);

Set this BitArray to the contents of ba.


void  init(void[] v, size_t numbits);

Map the BitArray onto v, with numbits being the number of bits in the array. Does not copy the data.

This is the inverse of opCast.


void[]  opCast(T : void[])();

Convert to void[].


size_t[]  opCast(T : size_t[])();

Convert to size_t[].


BitArray  opCom();

Support for unary operator ~ for BitArray.


BitArray  opAnd(BitArray e2);

Support for binary operator & for BitArray.


const BitArray  opOr(BitArray e2);

Support for binary operator | for BitArray.


const BitArray  opXor(BitArray e2);

Support for binary operator ^ for BitArray.


const BitArray  opSub(BitArray e2);

Support for binary operator - for BitArray.

a - b for BitArray means the same thing as a & ~b.


BitArray  opAndAssign(BitArray e2);

Support for operator &= for BitArray.


BitArray  opOrAssign(BitArray e2);

Support for operator |= for BitArray.


BitArray  opXorAssign(BitArray e2);

Support for operator ^= for BitArray.


BitArray  opSubAssign(BitArray e2);

Support for operator -= for BitArray.

a -= b for BitArray means the same thing as a &= ~b.


BitArray  opCatAssign(bool b);
BitArray  opCatAssign(BitArray b);

Support for operator ~= for BitArray.


const BitArray  opCat(bool b);
const BitArray  opCat_r(bool b);
const BitArray  opCat(BitArray b);

Support for binary operator ~ for BitArray.


const void  toString(scope void delegate(const(char)[]) sink, FormatSpec!char fmt);

Return a string representation of this BitArray.

Two format specifiers are supported:

  • %s which prints the bits as an array, and
  • %b which prints the bits as 8-bit byte packets
  • separated with an underscore.

    Examples
            BitArray b;
            b.init([0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]);
    
            auto s1 = format("%s", b);
            assert(s1 == "[0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1]");
    
            auto s2 = format("%b", b);
            assert(s2 == "00001111_00001111");
    

    pure nothrow @safe T  swapEndian(T)(T val) if (isIntegral!T || isSomeChar!T || isBoolean!T);

    Swaps the endianness of the given integral value or character.


    pure nothrow @safe auto  nativeToBigEndian(T)(T val) if (canSwapEndianness!T);

    Converts the given value from the native endianness to big endian and returns it as a ubyte[n] where n is the size of the given type.

    Returning a ubyte[n] helps prevent accidentally using a swapped value as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values).

    real is not supported, because its size is implementation-dependent and therefore could vary from machine to machine (which could make it unusable if you tried to transfer it to another machine).

    Examples
    int i = 12345;
    ubyte[4] swappedI = nativeToBigEndian(i);
    assert(i == bigEndianToNative!int(swappedI));
    
    double d = 123.45;
    ubyte[8] swappedD = nativeToBigEndian(d);
    assert(d == bigEndianToNative!double(swappedD));
    

    pure nothrow @safe T  bigEndianToNative(T, size_t n)(ubyte[n] val) if (canSwapEndianness!T && n == T.sizeof);

    Converts the given value from big endian to the native endianness and returns it. The value is given as a ubyte[n] where n is the size of the target type. You must give the target type as a template argument, because there are multiple types with the same size and so the type of the argument is not enough to determine the return type.

    Taking a ubyte[n] helps prevent accidentally using a swapped value as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values).

    Examples
    ushort i = 12345;
    ubyte[2] swappedI = nativeToBigEndian(i);
    assert(i == bigEndianToNative!ushort(swappedI));
    
    dchar c = 'D';
    ubyte[4] swappedC = nativeToBigEndian(c);
    assert(c == bigEndianToNative!dchar(swappedC));
    

    pure nothrow @safe auto  nativeToLittleEndian(T)(T val) if (canSwapEndianness!T);

    Converts the given value from the native endianness to little endian and returns it as a ubyte[n] where n is the size of the given type.

    Returning a ubyte[n] helps prevent accidentally using a swapped value as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values).

    Examples
    int i = 12345;
    ubyte[4] swappedI = nativeToLittleEndian(i);
    assert(i == littleEndianToNative!int(swappedI));
    
    double d = 123.45;
    ubyte[8] swappedD = nativeToLittleEndian(d);
    assert(d == littleEndianToNative!double(swappedD));
    

    pure nothrow @safe T  littleEndianToNative(T, size_t n)(ubyte[n] val) if (canSwapEndianness!T && n == T.sizeof);

    Converts the given value from little endian to the native endianness and returns it. The value is given as a ubyte[n] where n is the size of the target type. You must give the target type as a template argument, because there are multiple types with the same size and so the type of the argument is not enough to determine the return type.

    Taking a ubyte[n] helps prevent accidentally using a swapped value as a regular one (and in the case of floating point values, it's necessary, because the FPU will mess up any swapped floating point values. So, you can't actually have swapped floating point values as floating point values).

    real is not supported, because its size is implementation-dependent and therefore could vary from machine to machine (which could make it unusable if you tried to transfer it to another machine).

    Examples
    ushort i = 12345;
    ubyte[2] swappedI = nativeToLittleEndian(i);
    assert(i == littleEndianToNative!ushort(swappedI));
    
    dchar c = 'D';
    ubyte[4] swappedC = nativeToLittleEndian(c);
    assert(c == littleEndianToNative!dchar(swappedC));
    

    T  peek(T, Endian endianness = Endian.bigEndian, R)(R range) if (canSwapEndianness!T && isForwardRange!R && is(ElementType!R : const(ubyte)));
    T  peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && is(ElementType!R : const(ubyte)));
    T  peek(T, Endian endianness = Endian.bigEndian, R)(R range, size_t* index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && is(ElementType!R : const(ubyte)));

    Takes a range of ubytes and converts the first T.sizeof bytes to T. The value returned is converted from the given endianness to the native endianness. The range is not consumed.

    Parems:
    T = The integral type to convert the first T.sizeof bytes to. endianness = The endianness that the bytes are assumed to be in. range = The range to read from. index = The index to start reading from (instead of starting at the front). If index is a pointer, then it is updated to the index after the bytes read. The overloads with index are only available if hasSlicing!R is true.
    Examples
    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
    assert(buffer.peek!uint() == 17110537);
    assert(buffer.peek!ushort() == 261);
    assert(buffer.peek!ubyte() == 1);
    
    assert(buffer.peek!uint(2) == 369700095);
    assert(buffer.peek!ushort(2) == 5641);
    assert(buffer.peek!ubyte(2) == 22);
    
    size_t index = 0;
    assert(buffer.peek!ushort(&index) == 261);
    assert(index == 2);
    
    assert(buffer.peek!uint(&index) == 369700095);
    assert(index == 6);
    
    assert(buffer.peek!ubyte(&index) == 8);
    assert(index == 7);
    

    T  read(T, Endian endianness = Endian.bigEndian, R)(ref R range) if (canSwapEndianness!T && isInputRange!R && is(ElementType!R : const(ubyte)));

    Takes a range of ubytes and converts the first T.sizeof bytes to T. The value returned is converted from the given endianness to the native endianness. The T.sizeof bytes which are  read are consumed from the range.

    Parems:
    T = The integral type to convert the first T.sizeof bytes to. endianness = The endianness that the bytes are assumed to be in. range = The range to  read from.
    Examples
    ubyte[] buffer = [1, 5, 22, 9, 44, 255, 8];
    assert(buffer.length == 7);
    
    assert(buffer.read!ushort() == 261);
    assert(buffer.length == 5);
    
    assert(buffer.read!uint() == 369700095);
    assert(buffer.length == 1);
    
    assert(buffer.read!ubyte() == 8);
    assert(buffer.empty);
    

    void  write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && is(ElementType!R : ubyte));
    void  write(T, Endian endianness = Endian.bigEndian, R)(R range, T value, size_t* index) if (canSwapEndianness!T && isForwardRange!R && hasSlicing!R && is(ElementType!R : ubyte));

    Takes an integral value, converts it to the given endianness, and writes it to the given range of ubytes as a sequence of T.sizeof ubytes starting at index. hasSlicing!R must be true.

    Parems:
    T = The integral type to convert the first T.sizeof bytes to. endianness = The endianness to  write the bytes in. range = The range to  write to. index = The index to start writing to. If index is a pointer, then it is updated to the index after the bytes read.
    Examples
    {
        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
        buffer.write!uint(29110231u, 0);
        assert(buffer == [1, 188, 47, 215, 0, 0, 0, 0]);
    
        buffer.write!ushort(927, 0);
        assert(buffer == [3, 159, 47, 215, 0, 0, 0, 0]);
    
        buffer.write!ubyte(42, 0);
        assert(buffer == [42, 159, 47, 215, 0, 0, 0, 0]);
    }
    
    {
        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0, 0];
        buffer.write!uint(142700095u, 2);
        assert(buffer == [0, 0, 8, 129, 110, 63, 0, 0, 0]);
    
        buffer.write!ushort(19839, 2);
        assert(buffer == [0, 0, 77, 127, 110, 63, 0, 0, 0]);
    
        buffer.write!ubyte(132, 2);
        assert(buffer == [0, 0, 132, 127, 110, 63, 0, 0, 0]);
    }
    
    {
        ubyte[] buffer = [0, 0, 0, 0, 0, 0, 0, 0];
        size_t index = 0;
        buffer.write!ushort(261, &index);
        assert(buffer == [1, 5, 0, 0, 0, 0, 0, 0]);
        assert(index == 2);
    
        buffer.write!uint(369700095u, &index);
        assert(buffer == [1, 5, 22, 9, 44, 255, 0, 0]);
        assert(index == 6);
    
        buffer.write!ubyte(8, &index);
        assert(buffer == [1, 5, 22, 9, 44, 255, 8, 0]);
        assert(index == 7);
    }
    

    void  append(T, Endian endianness = Endian.bigEndian, R)(R range, T value) if (canSwapEndianness!T && isOutputRange!(R, ubyte));

    Takes an integral value, converts it to the given endianness, and appends it to the given range of ubytes (using put) as a sequence of T.sizeof ubytes starting at index. hasSlicing!R must be true.

    Parems:
    T = The integral type to convert the first T.sizeof bytes to. endianness = The endianness to write the bytes in. range = The range to  append to.
    Examples
    auto buffer = appender!(const ubyte[])();
    buffer.append!ushort(261);
    assert(buffer.data == [1, 5]);
    
    buffer.append!uint(369700095u);
    assert(buffer.data == [1, 5, 22, 9, 44, 255]);
    
    buffer.append!ubyte(8);
    assert(buffer.data == [1, 5, 22, 9, 44, 255, 8]);