Tedd.BitUtils
2.0.0
dotnet add package Tedd.BitUtils --version 2.0.0
NuGet\Install-Package Tedd.BitUtils -Version 2.0.0
<PackageReference Include="Tedd.BitUtils" Version="2.0.0" />
<PackageVersion Include="Tedd.BitUtils" Version="2.0.0" />
<PackageReference Include="Tedd.BitUtils" />
paket add Tedd.BitUtils --version 2.0.0
#r "nuget: Tedd.BitUtils, 2.0.0"
#:package Tedd.BitUtils@2.0.0
#addin nuget:?package=Tedd.BitUtils&version=2.0.0
#tool nuget:?package=Tedd.BitUtils&version=2.0.0
Tedd.BitUtils
Available as NuGet Package: https://www.nuget.org/packages/Tedd.BitUtils/
Fast bit manipulation extension methods for sbyte, byte, short (Int16), ushort (UInt16), int (Int32), uint (UInt32), long (Int64) and ulong (UInt64).
Every operation is available in two forms: in-place (modifies the variable via a ref extension method, avoiding a copy) and copy (returns a modified copy, leaving the original untouched, method name suffixed Copy).
Targets .NET 6, .NET 8 and .NET 10. Every operation is backed by System.Numerics.BitOperations and hardware intrinsics (POPCNT, LZCNT, TZCNT, BMI1, BMI2, BSWAP, ARM64 RBIT) where the CPU supports them, with an automatic runtime fallback where it doesn't - you never need to branch on this yourself. All methods are tagged for inline compilation.
Extension methods
Methods are implemented as extension methods, so your editor will list them when you type . after a supported type. Bit positions are zero based, counted from the least significant bit.
Get info
bool a = i.IsBitSet(n);int a = i.PopCount();— number of set bitsint a = i.Parity();— 1 if an odd number of bits are set, else 0int a = i.LeadingZeroCount();int a = i.TrailingZeroCount();int a = i.Log2();— floor(log2(i)), i.e. the position of the highest set bitint a = i.BitLength();— number of bits needed to represent i, i.e.Log2() + 1bool a = i.IsPowerOfTwo();string a = i.ToBitString();string a = i.ToBitStringPadded();
In-place
Operates directly on the variable, avoiding a copy.
i.SetBit(n, bool);i.SetBit0(n);/i.SetBit1(n);— faster thanSetBitwhen the state is a compile-time constanti.ToggleBit(n);i.Rol();/i.Rol(n);— rotate left one position, ornpositionsi.Ror();/i.Ror(n);i.ReverseBits();i.ReverseEndianness();i.RoundUpToPowerOf2();i.Pack(offset, length, i2);i.ExtractLowestSetBit();— isolate the lowest set bit (i & -i)i.ResetLowestSetBit();— clear the lowest set bit (i & (i - 1))i.GetMaskUpToLowestSetBit();— mask covering every bit up to and including the lowest set biti.ExtractHighestSetBit();i.ZeroHighBits(index);— keep only the lowindexbitsi.ZeroLowBits(index);— clear the lowindexbitsi.ParallelBitExtract(mask);— gather the bits selected bymaskinto consecutive low bits (PEXT)i.ParallelBitDeposit(mask);— scatter consecutive low bits into the positions selected bymask(PDEP)
Copy
Result is returned as a new value; the original variable is unchanged. Every in-place method above has a ...Copy counterpart, e.g.:
i2 = i.SetBitCopy(n, bool);i2 = i.RolCopy(n);i2 = i.ReverseBitsCopy();i2 = i.RoundUpToPowerOf2Copy();i2 = i.PackCopy(offset, length, i3);i2 = i.Unpack(offset, length);— the only naturally non-mutating "get" operation, so it has no in-place form
Simple example of usage
var a = 0;
a.SetBit(0, true);
// a == 1
a.SetBit(1, true);
// a == 3
a.SetBit0(0);
// a == 2
// a.IsBitSet(0) == false
var b = a.SetBitCopy(0, true);
// b == 3
a = 1;
a.Rol();
// a == 2
Pack / Unpack
Pack and Unpack copy a range of bits between two integers, similar to Substring but for bits. offset counts from the LSB (right) to the bit past the end of the field; length is the field's width.
var i1 = 0b0000_1111_1100_0011;
var i2 = 0b0000_0000_0000_0010;
// Copies the 2 lowest bits of i2 into bit positions 3 and 4 of i1 (offset 5, length 2: field is [offset-length, offset-1]).
i1.Pack(5, 2, i2);
// i1 is now: 0b0000_1111_1101_0011
var i3 = i1.Unpack(5, 2);
// i3 is now: 0b0000_0000_0000_0010
Performance
Rol()/Ror() (no count) are faster than Rol(1)/Ror(1), since no count needs to be masked to the type's bit width. Likewise SetBit0(n)/SetBit1(n) are faster than SetBit(n, bool) when the state is known at the call site, since no branch is needed.
Note that for sbyte, byte, short and ushort the CPU operates at 32-bit word size regardless, so there's no speed to be gained from the smaller datatypes themselves - the JIT-generated assembly for these operations ends up identical across the narrower integer types.
Hardware intrinsics
| Operation | Backing intrinsic (when supported) |
|---|---|
Rol, Ror |
BitOperations.RotateLeft/RotateRight (ROL/ROR) |
PopCount, Parity |
BitOperations.PopCount (POPCNT) |
LeadingZeroCount, BitLength |
BitOperations.LeadingZeroCount (LZCNT) |
TrailingZeroCount |
BitOperations.TrailingZeroCount (TZCNT) |
Log2 |
BitOperations.Log2 |
IsPowerOfTwo, RoundUpToPowerOf2 |
BitOperations.IsPow2/RoundUpToPowerOf2 |
ReverseEndianness |
BinaryPrimitives.ReverseEndianness (BSWAP) |
ReverseBits |
ARM64 RBIT; branch-free SWAR bit-swap on x86/x64 |
ExtractLowestSetBit/ResetLowestSetBit/GetMaskUpToLowestSetBit |
BMI1 (BLSI/BLSR/BLSMSK) |
ZeroHighBits |
BMI2 (BZHI) |
ParallelBitExtract/ParallelBitDeposit |
BMI2 (PEXT/PDEP); portable bit-by-bit fallback elsewhere |
Every entry above falls back automatically to a portable software implementation on CPUs or platforms without the matching instruction (e.g. ARM without RBIT for ReverseBits, or x86 without BMI2 for ParallelBitExtract/ParallelBitDeposit) - there's no configuration or feature flag involved.
Benchmarks
src/Tedd.BitUtils.Benchmarks uses BenchmarkDotNet to compare this version against a frozen snapshot of the pre-2.0 implementation (src/Tedd.BitUtils.Archive), operation by operation. Run it with:
cd src/Tedd.BitUtils.Benchmarks
dotnet run -c Release
or target one comparison directly, e.g. dotnet run -c Release --filter *ReverseBits*.
Changelog
2.0.0
- Breaking: now targets .NET 6, .NET 8 and .NET 10 only. .NET Framework and .NET Standard consumers should stay on the 1.x line.
- All operations now go through
System.Numerics.BitOperationsand hardware intrinsics directly, rather than each method doing its ownIsSupportedcheck. - New operations:
ToggleBit,Parity,TrailingZeroCount,Log2,BitLength,IsPowerOfTwo,RoundUpToPowerOf2,ExtractLowestSetBit,ResetLowestSetBit,GetMaskUpToLowestSetBit,ExtractHighestSetBit,ZeroHighBits,ZeroLowBits,ParallelBitExtract,ParallelBitDeposit. Rol/Rorare now implemented forsbyte/byte/short/ushorttoo (previously 32/64-bit only), and every operation now has ansbyteoverload.- Fixed:
ReverseEndiannessnever actually took its fast path in 1.x, due to a compile constant that was never defined anywhere in the project - it silently ran the manual software fallback on every call, on every target framework, even whereBinaryPrimitives.ReverseEndianness(a single BSWAP) was available. It now always uses the intrinsic-backed path. - Fixed:
Pack/Unpackforint/uint/long/ulongcomputed their mask as(1 << length) - 1(or(1 << offset) - 1forUnpack); whenlength/offsetequalled the full width of the type, that shift count wrapped to a no-op and silently produced a mask of 0 instead of all-ones. ReverseBitson x86/x64 now uses a branch-free SWAR bit-swap instead of four table lookups.
License
MIT - see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net6.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Tedd.BitUtils:
| Package | Downloads |
|---|---|
|
schema
Library for converting classes to and from binary. Provides a C# Roslyn generator that automatically implements conversion logic for simple classes. |
GitHub repositories
This package is not used by any popular GitHub repositories.
2.0.0: BREAKING - now targets net6.0, net8.0 and net10.0 only; .NET Framework and .NET Standard consumers must stay on 1.x. Every operation is now backed directly by System.Numerics.BitOperations and hardware intrinsics (POPCNT, LZCNT, TZCNT, BMI1, BMI2, BSWAP, ARM64 RBIT). New: ToggleBit, Parity, TrailingZeroCount, Log2, BitLength, IsPowerOfTwo, RoundUpToPowerOf2, ExtractLowestSetBit, ResetLowestSetBit, GetMaskUpToLowestSetBit, ExtractHighestSetBit, ZeroHighBits, ZeroLowBits, ParallelBitExtract, ParallelBitDeposit, Rol/Ror for SByte/Byte/Int16/UInt16, plus SByte overloads throughout. Fixed: ReverseEndianness previously never took the intrinsic path due to an always-false compile constant and always ran the naive software fallback; it now always calls BinaryPrimitives.ReverseEndianness. Faster ReverseBits on x86/x64 (branch-free SWAR instead of table lookups).