Click here to Skip to main content
15,920,896 members
Articles / Programming Languages / C#
Tip/Trick

C# Binary Literal Helper Class

Rate me:
Please Sign up or sign in to vote.
4.69/5 (13 votes)
22 Mar 2016CPOL2 min read 51.8K   11   28
C# doesn't allow for binary literals (e.g. int bits = 0b00010101). With the helper class presented here, you can do something like that, as close as possible.

Introduction

When you're dealing with bitwise operations, the possibility to initialize variables or constants with binary literals would be helpful so that you could read the value the way it will be interpreted by your code.

Background

Initially, it was planned for C# 6.0 to introduce binary literals. Unfortunately, this feature wasn't implemented and is now scheduled for C# 7.0.

So I wrote a small helper class which allows you to do something like that, as close as possible.

Using the Code

The following class allows you to write a binary literal in the form of a string literal which will be converted into an integer of choice:

C#
public static class BinaryLiteral
{
    public static byte BinaryLiteralToByte(this string str)
    {
        return ToByte(str);
    }

    public static short BinaryLiteralToInt16(this string str)
    {
        return ToInt16(str);
    }

    public static int BinaryLiteralToInt32(this string str)
    {
        return ToInt32(str);
    }

    public static long BinaryLiteralToInt64(this string str)
    {
        return ToInt64(str);
    }

    public static byte ToByte(string str)
    {
        return (byte)ToInt64(str, sizeof(byte));
    }

    public static short ToInt16(string str)
    {
        return (short)ToInt64(str, sizeof(short));
    }

    public static int ToInt32(string str)
    {
        return (int)ToInt64(str, sizeof(int));
    }

    public static long ToInt64(string str)
    {
        return ToInt64(str, sizeof(long));
    }

    private static long ToInt64(string str, int sizeInBytes)
    {
        int sizeInBits = sizeInBytes * 8;
        int bitIndex = 0;
        long result = 0;

        for (int i = str.Length - 1; i >= 0; i--)
        {
            char c = str[i];

            if (c != ' ')
            {
                if (bitIndex == sizeInBits)
                {
                    throw new OverflowException("binary literal too long: " + str);
                }

                if (c == '1')
                {
                    result |= 1L << bitIndex;
                }
                else if (c != '0')
                {
                    throw new InvalidCastException(String.Format("invalid character '{0}' in binary literal: {1}", c, str));
                }

                bitIndex++;
            }
        }

        return result;
    }
}

Then, you can use it like this:

C#
static byte myByteBitMask = BinaryLiteral.ToByte("                        0110 1100");
static short myInt16BitMask = BinaryLiteral.ToInt16("           1000 0000 0110 1100");
static int myInt32BitMask = BinaryLiteral.ToInt32("   0101 1111 1000 0000 0110 1100");
static long myInt64BitMask = BinaryLiteral.ToInt64("  0101 1111 1000 0000 0110 1100");

Or, if you prefer extension methods, like this:

C#
static byte myByteBitMask = "                       0110 1100".BinaryLiteralToByte();
static short myInt16BitMask = "           1000 0000 0110 1100".BinaryLiteralToInt16();
static int myInt32BitMask = "   0101 1111 1000 0000 0110 1100".BinaryLiteralToInt32();
static long myInt64BitMask = "  0101 1111 1000 0000 0110 1100".BinaryLiteralToInt64();

Separation of "bit-blocks" with blanks is optional and can be used at any position.

If you leave out high bits (in the above example, I skipped the 8 high bits of the Int32 and the 40 high bits of the Int64), they're treated as zeroes.

An unavoidable limitation of this solution is that you can't use it to initialize a constant identifier. So you would have to use readonly variables instead of const identifiers.

As the binary literal is a string constant, the variables you initialize with these methods normally should be static to avoid repeated conversion.

Points of Interest

If you want to use underscores instead of blanks to separate "bit-blocks", just replace if (c != ' ') with if (c != '_') - or, to allow both: if (c != ' ' && c != '_') .

You could achieve the same thing as this class by using only .NET-standard methods:

C#
static int myBitMask = Convert.ToInt32("1000 0000 0110 1100".Replace(" ", ""), 2);

But it's more to type and it's less descriptive, which, I believe, is an important aspect of code.

If you favor switch-statements over if-statements take a look at Richard Deeming's proposed alternative way of writing the conversion code in his comment below.

History

  • 22nd Mar, 2016: Added extension methods
  • 24th Feb, 2016: Added hint about "static usage" and some other small improvements
  • 23rd Feb, 2016: Some small improvements on readibility, conciseness and performance
  • 22nd Feb, 2016: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
1985: "Hello world" on a Colour Genie
1989: Basic Balls of Mud on an Amiga 500
1994: C++ Big Balls of Mud on a PC
2005: C# Smaller Balls of Mud

Avatar image © dpa (licensed).



Comments and Discussions

 
SuggestionFinally Implemented with Visual Studio 15 preview Pin
Banketeshvar Narayan2-Apr-16 20:59
professionalBanketeshvar Narayan2-Apr-16 20:59 
GeneralRe: Finally Implemented with Visual Studio 15 preview Pin
Sascha Lefèvre10-Apr-16 14:10
professionalSascha Lefèvre10-Apr-16 14:10 
Generalthoughts about 29-bit binary literal Pin
jediYL28-Mar-16 17:38
professionaljediYL28-Mar-16 17:38 
GeneralRe: thoughts about 29-bit binary literal Pin
Sascha Lefèvre28-Mar-16 23:16
professionalSascha Lefèvre28-Mar-16 23:16 
QuestionEnhanced C# Pin
Qwertie28-Mar-16 16:54
Qwertie28-Mar-16 16:54 
AnswerRe: Enhanced C# Pin
Sascha Lefèvre28-Mar-16 23:26
professionalSascha Lefèvre28-Mar-16 23:26 
QuestionWhy not a new value type? Pin
Rafael Nicoletti22-Mar-16 15:32
Rafael Nicoletti22-Mar-16 15:32 
Questionsome comments Pin
sx200825-Feb-16 9:25
sx200825-Feb-16 9:25 
AnswerRe: some comments Pin
Sascha Lefèvre25-Feb-16 10:05
professionalSascha Lefèvre25-Feb-16 10:05 
QuestionHighly inefficient Pin
irneb24-Feb-16 3:52
irneb24-Feb-16 3:52 
AnswerRe: Highly inefficient Pin
Sascha Lefèvre24-Feb-16 4:17
professionalSascha Lefèvre24-Feb-16 4:17 
AnswerRe: Highly inefficient Pin
webmaster44224-Feb-16 23:49
webmaster44224-Feb-16 23:49 
GeneralRe: Highly inefficient Pin
irneb25-Feb-16 0:53
irneb25-Feb-16 0:53 
GeneralRe: Highly inefficient Pin
Qwertie28-Mar-16 17:03
Qwertie28-Mar-16 17:03 
SuggestionSlight improvement Pin
Richard Deeming22-Feb-16 9:19
mveRichard Deeming22-Feb-16 9:19 
GeneralRe: Slight improvement Pin
Sascha Lefèvre22-Feb-16 12:06
professionalSascha Lefèvre22-Feb-16 12:06 
Hi Richard,

thank you for your suggestions! But there's a problem with the foreach-loop: It iterates in the wrong direction Wink | ;) . Because the amount of spaces in the string is unknown it can't be fixed with result |= 1L << (str.Length - 1 - bitIndex);. So you would either have to count the actual bits in the string beforehand or reverse the string.

As we're now definitely entering micro-optimization Wink | ;) I ran a benchmark with:
- My current implementation
- Your proposed implementation but the foreach-loop replaced by a for-loop
- Your proposed implementation with counting the actual bits in the string:
C#
// ...
int maxBitIndex = -1;
foreach (char c in str)
    if (c != ' ')
        maxBitIndex++;

foreach (char c in str)
{
   // ...
   case '1': result |= 1L << (maxBitIndex - bitIndex); bitIndex++; break;
   // ...
}
// ...
The approach to reverse the string was much slower in a shorter, precursory benchmark so I left it out of the longer one:
Total time: 00:12:48

// * Summary *
BenchmarkDotNet=v0.8.2.0
OS=Microsoft Windows NT 6.1.7601 Service Pack 1
Processor=Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz, ProcessorCount=8
Freq=3330156 ticks, Resolution=300.2862 ns [HighResolution]
HostCLR=MS.NET 4.0.30319.42000, Arch=32-bit RELEASE

Type=BLB  Mode=Throughput  Platform=HostPlatform
Jit=HostJit  .NET=HostFramework  toolchain=Classic
Runtime=Clr  Warmup=5  Target=10

                        Method |       AvrTime |     Error |
------------------------------ |-------------- |---------- |
                Current_ToByte |   367.8226 ns | 0.1825 ns |
               Current_ToShort |   669.7810 ns | 0.6249 ns |
                 Current_ToInt | 1,513.3351 ns | 1.0641 ns |
                Current_ToLong | 3,153.8170 ns | 2.0245 ns |

    ProposedWithForLoop_ToByte |   381.9125 ns | 0.2539 ns |
   ProposedWithForLoop_ToShort |   702.3716 ns | 2.4004 ns |
     ProposedWithForLoop_ToInt | 1,610.7230 ns | 1.1981 ns |
    ProposedWithForLoop_ToLong | 3,285.8105 ns | 2.3101 ns |

  ProposedWithCountBits_ToByte |   547.1906 ns | 0.5884 ns |
 ProposedWithCountBits_ToShort | 1,120.4673 ns | 2.3502 ns |
   ProposedWithCountBits_ToInt | 2,443.0517 ns | 0.9951 ns |
  ProposedWithCountBits_ToLong | 5,026.2556 ns | 4.9387 ns |
In case you're curious, the benchmark methods look like this:
C#
[Benchmark]
[BenchmarkTask(warmupIterationCount: 5, targetIterationCount: 20)]
public int Current_ToByte()
{
    byte a = BinaryLiteral_Current.ToByte("1010 1010");
    byte b = BinaryLiteral_Current.ToByte("1010 1010");
    byte c = BinaryLiteral_Current.ToByte("1010 1010");
    byte d = BinaryLiteral_Current.ToByte("1010 1010");
    byte e = BinaryLiteral_Current.ToByte("1010 1010");
    byte f = BinaryLiteral_Current.ToByte("1010 1010");
    byte g = BinaryLiteral_Current.ToByte("1010 1010");
    byte h = BinaryLiteral_Current.ToByte("1010 1010");
    byte i = BinaryLiteral_Current.ToByte("1010 1010");
    byte j = BinaryLiteral_Current.ToByte("1010 1010");
    byte k = BinaryLiteral_Current.ToByte("1010 1010");
    byte l = BinaryLiteral_Current.ToByte("1010 1010");
    byte m = BinaryLiteral_Current.ToByte("1010 1010");
    byte n = BinaryLiteral_Current.ToByte("1010 1010");
    byte o = BinaryLiteral_Current.ToByte("1010 1010");
    byte p = BinaryLiteral_Current.ToByte("1010 1010");
    return unchecked(a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p);
}
As you can see, my current implementation is actually the fastest here. I'm speculating that the array-bounds-check (which actually takes place because it's a decending for-loop) is still less overhead than the enumerator for the foreach-loop and that the switch-statement can't play its strength because there are so few cases. I would have expected that the compiler changes it into if-else-statements for this reason but that's actually not the case. Also I would have expected that the compiler moves the calculation of the maximum bit index out of the loop (of my current implementation) but that's not the case either (despite optimizations turned on).

However, I'll update the Tip/Trick with some style improvements Smile | :)

cheers, Sascha
If the brain were so simple we could understand it, we would be so simple we couldn't. — Lyall Watson


modified 22-Feb-16 20:32pm.

GeneralRe: Slight improvement Pin
Richard Deeming23-Feb-16 1:40
mveRichard Deeming23-Feb-16 1:40 
GeneralRe: Slight improvement Pin
Sascha Lefèvre23-Feb-16 3:27
professionalSascha Lefèvre23-Feb-16 3:27 
GeneralRe: Slight improvement Pin
Richard Deeming23-Feb-16 3:31
mveRichard Deeming23-Feb-16 3:31 
GeneralRe: Slight improvement Pin
Sascha Lefèvre23-Feb-16 3:44
professionalSascha Lefèvre23-Feb-16 3:44 
SuggestionAlternative Approach... Pin
Andrew Rissing22-Feb-16 6:51
Andrew Rissing22-Feb-16 6:51 
GeneralRe: Alternative Approach... Pin
Sascha Lefèvre22-Feb-16 7:12
professionalSascha Lefèvre22-Feb-16 7:12 
GeneralRe: Alternative Approach... Pin
Andrew Rissing22-Feb-16 7:26
Andrew Rissing22-Feb-16 7:26 
GeneralRe: Alternative Approach... Pin
Sascha Lefèvre22-Feb-16 8:25
professionalSascha Lefèvre22-Feb-16 8:25 
GeneralRe: Alternative Approach... Pin
Andrew Rissing22-Feb-16 8:39
Andrew Rissing22-Feb-16 8:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.