Skip to content

Instantly share code, notes, and snippets.

@crozone
Created July 4, 2023 08:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save crozone/095d83ebc15796438f89a4288ff7db10 to your computer and use it in GitHub Desktop.
Save crozone/095d83ebc15796438f89a4288ff7db10 to your computer and use it in GitHub Desktop.
C# Round down to next power of two. Analogous to BitOperations.RoundUpToPowerOf2().
using System.Numerics;
/// <summary>
/// Round the given integral value down to a power of 2.
/// This is the equivalent to returning a number with a single bit set at the most significant location a bit is set in the input value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// The smallest power of 2 which is less than or equal to <paramref name="value"/>.
/// If <paramref name="value"/> is 0 or the result overflows, returns 0.
/// </returns>
uint RoundDownToPowerOf2(uint value) => (uint)(0x8000_0000ul >> BitOperations.LeadingZeroCount(value));
// Reference: System.Numerics.BitOperations.RoundUpToPowerOf2()
// uint RoundUpToPowerOf2(uint value) => (uint)(0x1_0000_0000ul >> BitOperations.LeadingZeroCount(value - 1));
// Note: Bitmask must be an unsigned long for input of 0 to create an output of 0.
// Why: In C#, when you shift by more bits than the integer width, the higher bits of the shift value are ignored
// and the shift effectively wraps around. UInt32 << shift is equal to UInt32 << (shift & 0x1F).
// For input value = 0, LeadingZeroCount is 32, which is 6 bits, so shifting by 32 is the same as shifting by 0.
// This leads to the wrong result for value = 0. Shifting a UInt64 increases the limit to 64 shifts (shift & 0x3F).
// The other option is to use (1u ^ (uint)(shift >> 5)) as the bitmask.
// This produces a 1 if shift is 5 bits or less, and 0 if the 6th bit is set, leading to the correct result.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment