1

I want to determine the number of bits needed for a generic integral binary data type (a nice new feature of .NET 7 -- see IBinaryInteger<T>) inside a method. I don't want to use "sizeof" because it requires unsafe compilation. I've come up with a solution (actually several), but have a tough time believing there's not a better answer.

Here's one solution, along with a test program:

using System.Numerics;

namespace BitCountApp {

    internal class Program {

        static void Main(string[] args) {
            Console.WriteLine("Bits in byte = " + CountBits<byte>());
            Console.WriteLine("Bits in ushort = " + CountBits<ushort>());
            Console.WriteLine("Bits in uint = " + CountBits<uint>());
            Console.WriteLine("Bits in ulong = " + CountBits<ulong>());
            Console.WriteLine("Bits in UInt128 = " + CountBits<UInt128>());
        }

        static private int CountBits<T>() where T : IBinaryInteger<T> {
            return T.AllBitsSet.GetByteCount() * 8;
        }

    }

}

This solution works, and it's nice and compact, but it works only for data sizes that are multiples of 8. This may be more theoretical than practical, but it would fail for a "UInt7" datatype that implemented IBinaryInteger with a 7-bit unsigned integer.

"Ah! PopCount!" I hear you cry. Well, there's a problem. If I try to implement CountBits using PopCount ...

static private int BitsForType<T>() where T : IBinaryInteger<T> {
    return (int)(T.PopCount(T.AllBitsSet));
}

... I find that I cannot cast the value of PopCount, which returns a "T", to "int" (even though T is a binary integer type).

There is a solution that works correctly -- counting the number of bits for myself, like this ...

static private int CountBits<T>() where T : IBinaryInteger<T> {
    var ones = T.AllBitsSet;
    int count = 0;
    while (ones != T.Zero) {
        count++;
        ones >>= 1;
    };
    return count;
}

... but, Yuk!

Is there a way to do this without counting the bits myself? I'm sort of shocked there isn't a built-in property or method for this.

TylerH
  • 20,799
  • 66
  • 75
  • 101
BoCoKeith
  • 817
  • 10
  • 21
  • 1
    FWIW, I agree that it seems odd that `PopCount` doesn't just return an `int` result. Integer types with a numbers of bits greater than what can be represented in an `int` would be impractical at best. – 500 - Internal Server Error May 20 '23 at 21:54
  • @500 It's particularly odd since various overloads of BitOperations.PopCount (for UIntPtr, UInt32, and UInt64) do exactly that, i.e. return an int. – BoCoKeith May 20 '23 at 21:58
  • You can't expect Microsoft to provide and maintain a fully complete library of all functions one can think of. They only offer commonly used ones. Apparently what you want isn't in high demand. – JHBonarius May 20 '23 at 22:02
  • 2
    You're probably going to have more luck [asking this question at the dotnet repo](https://github.com/dotnet/runtime/discussions/categories/q-a). – Ian Kemp May 20 '23 at 22:12
  • `Unsafe.SizeOf` and `Marshal.SizeOf` do not need unsafe compilation IIRC, did you try them? – Ray May 21 '23 at 07:58
  • @Ray, I hadn't tried, but I did and they work. However, all "sizeof" solutions return byte counts, and not bit counts, and so would not work for the hypothetical UInt7 type. – BoCoKeith May 21 '23 at 15:15
  • Just to be clear, there is no and will never be no UInt7 type. C# and .Net aren't like C which leaves a lot of details out of the spec for hypothetical computer architectures that will never exist, the smallest addresable data type is `byte`, which is defined precisely as an 8 bit unsigned integer. – Blindy May 21 '23 at 15:37

1 Answers1

0

Here's a great answer provided by @huoyaoyuan on GitHub that uses the CreateChecked method (thanks for the suggestion @Ian):

Use int.CreateXXX to cast.

So I would have:

static private int CountBits<T>() where T : IBinaryInteger<T> {
    return int.CreateChecked(T.PopCount(T.AllBitsSet));
}

There are also CreateSaturating and CreateTruncating methods that behave slightly differently.

TylerH
  • 20,799
  • 66
  • 75
  • 101
BoCoKeith
  • 817
  • 10
  • 21