Click here to Skip to main content
15,881,027 members
Articles / Programming Languages / C# 6.0

To And From Binary Literals

Rate me:
Please Sign up or sign in to vote.
3.80/5 (6 votes)
5 Apr 2016CPOL10 min read 8.9K   79   4  
To And From Binary Literals for all standard .net numeric value types.

Introduction

Lately i read a codeproject article about interpreting binary literals. This limited itself to interpreting a binary literal towards a value type but didn't do this vice-versa. Often while debugging i've quickly wanted to check if that value coming in having that bytemask has that certain bit or not. So i wanted to create something which could go to and from. I actually had had this idea for many years, but you know how it is, you never start. Thanks to the article i actually found the will/time to do so. The result is a little framework which allows the following types to be converted to and from: byte, sbyte, ushort, short, uint, int, ulong, long, float, double, decimal and BigInteger.

Background

The code makes use of .net's functionality to make hex-code. The hex-code gets translated to a binary literal. In case of floating point variables i use some small tricks of System.Bitconvertor or Biginteger to convert the values into integer values. Recreating values from binary literals kinda does the same.

I wanted to create a castable way of working with the framework, which will be described in the "Using the code" section. Equals and GetHashCode are implemented, though i strongly advise to not build robust code upon this framework, against using normal value types, the conversions take a lot of time against them.

The code can throw a lot of different errors, all castings from and towards valuetypes will fire as if you would do a normal cast between them, is it not allowed in the normal situation then it is not allowed now either.

Besides all the expected errors, other errors can be raised as well: Null Exception in case a parameter is supplied null and it's not correct to return a null on that function. An Argument Exception in case a larger literal is supplied then the specified valuetype can handle or when a type is specified which is not supported.

I kept the code compatible with c# 4.0 so the class-file itself can be re-used from Visual Studio 2010 and higher. I made the example in Visual studio 2015.

Because i've decided to also offer the BigInteger it means you'll have to reference the System.Numerics framework to work with this little framework.

How The Code Works

Firstly i made a small object extender to deal with null testing.

internal static class ObjectExtender
{
    internal static bool IsInitialized(this object ToTest)
    {
        return !object.ReferenceEquals(ToTest, null);
    }
    internal static bool IsNull(this object ToTest)
    {
        return object.ReferenceEquals(ToTest, null);
    }
}

I made this extender to test on null more easily. I use this normaly within an assembly in my code. Why you might wonder. If u check on null and the class/struct u're checking on has implemented it's own equality it's operator will be triggered. So if it's a badly written test, doesn't take null into account, takes long, ... it will trouble execution, for a check on null. Why c# does this is actually not clear to me, they shall have a good reason, but personally i would liked to have seen operators not to be triggered on null comparisons.

Main Constructor

The i created the main/first constructor

public Binarie(string value, Type valueType = default(Type))
{
    if (ConfirmValidLiteralString(value))
    {
        this._value = Format(value);
        string  Temp    = _value.Replace(" ", "");
        int     Length  = Temp.Length / 8;

        if (((decimal)Temp.Length) / ((decimal)8) > Length)
        {
            Length++;
        }

        if (valueType.IsNull())
        {
            //must be user call detect closest type.
            if (Length == 1)
            {
                _TypeOfValue = typeof(byte);
            }
            else if (Length == 2)
            {
                _TypeOfValue = typeof(ushort);
            }
            else if (Length <= 4)
            {
                _TypeOfValue = typeof(uint);
            }
            else if (Length <= 8)
            {
                _TypeOfValue = typeof(ulong);
            }
            else
            {
                _TypeOfValue = typeof(BigInteger);
            }
        }
        else
        {
            int Needed = SizeOfType(valueType);

            if (Needed == 0)
            {
                throw new ArithmeticException("Type not supported");
            }
            else if ((Length <= Needed) || (Needed == -1))
            {
                this._TypeOfValue = valueType;
            }
            else
            {
                throw new ArgumentException("To many bits supplied for specified valuetype");
            }
        }
    }
    else if (valueType.IsInitialized() && (SizeOfType(valueType) != 0))
    {
        _value = "0";
        this._TypeOfValue = valueType;
    }
    else
    {
        throw new ArgumentException("Illegal value specified.");
    }

    if (_TypeOfValue == typeof(byte))
    {
        _ValueCache = unchecked((byte)ConvertUnsigned(byte.MinValue, byte.MaxValue));
    }
    else if (_TypeOfValue == typeof(sbyte))
    {
        _ValueCache = unchecked((sbyte)ConvertUnsigned(sbyte.MinValue, sbyte.MaxValue));
    }
    else if (_TypeOfValue == typeof(ushort))
    {
        _ValueCache = unchecked((ushort)ConvertUnsigned(ushort.MinValue, ushort.MaxValue));
    }
    else if (_TypeOfValue == typeof(short))
    {
        _ValueCache = unchecked((short)ConvertUnsigned(short.MinValue, short.MaxValue));
    }
    else if (_TypeOfValue == typeof(uint))
    {
        _ValueCache = unchecked((uint)ConvertUnsigned(uint.MinValue, uint.MaxValue));
    }
    else if (_TypeOfValue == typeof(int))
    {
        _ValueCache = unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue));
    }
    else if (_TypeOfValue == typeof(ulong))
    {
        _ValueCache = unchecked((ulong)ConvertUnsigned(ulong.MinValue, ulong.MaxValue));
    }
    else if (_TypeOfValue == typeof(long))
    {
        _ValueCache = unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue));
    }
    else if (_TypeOfValue == typeof(float))
    {
        _ValueCache = BitConverter.ToSingle(BitConverter.GetBytes(unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue))), 0);
    }
    else if (_TypeOfValue == typeof(double))
    {
        _ValueCache = BitConverter.Int64BitsToDouble(unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue)));
    }
    else if (_TypeOfValue == typeof(decimal))
    {
        BigInteger Temp;

        if (BigInteger.TryParse(ConvertBitsToHex(_value),
                System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
                System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
        {
            _ValueCache = (decimal)Temp;
        }
        else
        {
            throw new ArgumentException("Value cannot be converted to decimal.");
        }
    }
    else if (_TypeOfValue == typeof(BigInteger))
    {
        BigInteger Temp;

        if (BigInteger.TryParse(ConvertBitsToHex(_value),
                System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
                System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
        {
            _ValueCache = Temp;
        }
        else
        {
            throw new ArgumentException("Value cannot be converted to BigInteger.");
        }
    }
    else
    {
        throw new System.NotSupportedException("Only the standard numeric valuetypes are supported.");
    }

    SetHash();
}

As you can see it's huge.

Firstly it check if supplied string does only contain 0,1 and spaces. If the string contains anything else a error will be thrown. If the string does not contain a 0 or a 1 it will be determined empty. If the string is empty and a Type has been specified, and the Type specified is a supported Type then the Binarie will be initialised with a Binary Literal 0 string. If no Type is specified while the string is empty an error will be raised.

After this step the string is formatted 4 bits 1 space and saved in cache-variable _value. Then formatted string is stripped of all spaces and the length in bytes is calculated. If a supported Type has been supplied now it's checked if the supplied bits fit in the amount of bytes the specified Type has, if not an error will be raised. If no Type has been supplied it will select the most fitting unit to work with and select this as Type.

When _value and _ TypeOfValue  are determined the _valueCache will be filled with a real instance of specified Type. So when this original constructor is used the value which is placed in _valueCache has been recreated from a binary literal.

I skipped some special conversions to make above possible

_ValueCache = BitConverter.ToSingle(BitConverter.GetBytes(unchecked((int)ConvertUnsigned(int.MinValue, int.MaxValue))), 0);

ConverUnsigned is used for all values smaller or equal then a ulong. This function creates a ulong from the bits. Then it checks if the resulting value corresponds to the supplied range. The result is returned and the reciever makes an unchecked cast to needed value type.

In above case this means an int, while the result results in a float. System.Bitconverter has the possibility to get the bytes for a value type and the possibility to create a float from bytes. This trick i use to convert from int to float, and vice-versa. Cause of this step i can use a standard int.ToString("X8") to make hex-code.

_ValueCache = BitConverter.Int64BitsToDouble(unchecked((long)ConvertUnsigned(long.MinValue, long.MaxValue)));

For the double it's easier, System.BitConvertor offers a direct function.

Then i wanted to support the decimal, but System.Bitconvertor could not help me out. It turned out System.Numbers.BigInteger can be created from a decimal and cast be casted towards a decimal. BigInteger does support ToString("X") (a to hex method). Secondly BigInteger can parse an hex string into a value. I had found my answer.

BigInteger Temp;

if (BigInteger.TryParse(ConvertBitsToHex(_value),
    System.Globalization.NumberStyles.HexNumber | System.Globalization.NumberStyles.AllowHexSpecifier,
    System.Globalization.CultureInfo.CurrentCulture.NumberFormat, out Temp))
{
    _ValueCache = (decimal)Temp;
}
else
{
    throw new ArgumentException("Value cannot be converted to decimal.");
}

The binary literal is converted into hex-code by the function ConvertBitsToHex, then BigInteger tries to parse the string, on success we can cast the BigInteger into a decimal (if it's value is within it's range, which should because we created it from a decimal). If it couldn't an error will be thrown. The BigInteger from Binary Literal works the same, but then without the decimal cast.

Internal Constructor

To support value (byte, sbyte, ushort, short, uint, int, ulong, long, float, double, decimal, BigInteger) directly to Binarie i made an internal constructor which will skip all above, to save time.

internal Binarie(string value, Type ValueType, object RealValue)
{
    this._value         = value;
    this._TypeOfValue   = ValueType;
    this._ValueCache    = RealValue;

    SetHash();
}

Then i made a operator for each specific supported value type.

public static implicit operator Binarie(byte ToConvert)
{
    return new Binarie(ConvertHexToBits(ToConvert.ToString("X2")), typeof(byte), ToConvert);
}

Firstly the incoming value will be converted to hex with use of normal .net functionality. Then this hex value is transformed into a binary literal. The constructor is called with the result string, type and original value. So in this creation the string is not reinterpreted back into the _valueCache nor does any check occur, which saves time.

Hash

Both constructors fill the hash variable at the end. This hash consists of getting a GetHashCode on the combined string of the Binary Literal and the name of the Type. This should bring an equally strong hash then the hash of a string.

Casting back to requested value type

Casting back to requested value type is supported by implementing an operator for each supported value type

public static implicit operator byte(Binarie ToConvert)
{
    byte Result = 0;

    if (ToConvert.IsNull())
    {
        throw new NullReferenceException("ToConvert cannot be null.");
    }

    if (ToConvert._TypeOfValue == typeof(decimal))
    {
        Result = (byte)(decimal)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(BigInteger))
    {
        Result = (byte)(BigInteger)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(float))
    {
        Result = (byte)(float)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(double))
    {
        Result = (byte)(double)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(sbyte))
    {
        Result = (byte)(sbyte)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(short))
    {
        Result = (byte)(short)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(int))
    {
        Result = (byte)(int)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(long))
    {
        Result = (byte)(long)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(ushort))
    {
        Result = (byte)(ushort)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(uint))
    {
        Result = (byte)(uint)(ToConvert._ValueCache);
    }
    else if (ToConvert._TypeOfValue == typeof(ulong))
    {
        Result = (byte)(ulong)(ToConvert._ValueCache);
    }
    else
    {
        Result = (byte)(ToConvert._ValueCache);
    }

    return Result;
}

Firstly the incoming Binarie is checked for not being null, else an error will be thrown.

Secondly the type of the value of the Binarie will be determined and a cast through it's original type towards the requested type is made. The result is then converted to specified needs and will be returned.

Supporting casting with value type specifier

I wanted to support type specified Binaries, firstly i though of inheritance, but that would give a penalty. There would be no way to cast from Binarie to it's corresponding Binarie<>. Because of this i decided to delegate the functionality from a new generic class. I narrowed the allowed types with where TType:struct so that all class variables at least trigger an error.

public class Binarie<TType> : IBinarie
    where TType:struct
{
    private static readonly Type    OurType = typeof(TType);
    private                 Binarie Parent  = null;
    private                 int     Hash    = 0;

    private Binarie(string Value, object RealValue)
    {
        Parent = new Binarie(Value, OurType, RealValue);
        SetHash();
    }

    public Binarie(string Value)// : base(Value, typeof(TType))
    {
        if ((OurType == typeof(sbyte)) ||
            (OurType == typeof(byte)) ||
            (OurType == typeof(short)) ||
            (OurType == typeof(ushort)) ||
            (OurType == typeof(int)) ||
            (OurType == typeof(uint)) ||
            (OurType == typeof(long)) ||
            (OurType == typeof(ulong)) ||
            (OurType == typeof(float)) ||
            (OurType == typeof(double)) ||
            (OurType == typeof(decimal)) ||
            (OurType == typeof(BigInteger)))
        {
            Parent = new Binarie(Value, OurType);
        }
        else
        {
            throw new System.NotSupportedException("Only the standard numeric valuetypes are supported.");
        }
        SetHash();
    }
...
operators, from to written out per value type
...
}

Per TType there's on static readonly variable OurType which specifies which Type we are. Secondly there's an Parent instance to the Binarie, which does all the real work. When the constructor is called the specified type is checked, if it is not supported an error will be raised. Else a Binarie will be initiated with specified Binary Literal string and OurType.

For the same reason as the internal constructor of Binarie, this class has a private constructor which constructs a Binarie through the internal constructor.

Hash

Both constructors calculate the Hash at end which is also a GetHashCode from a string build upon from the Binary Literal string and the name of the Type, but to differ from the Binarie also the suffix TType is added.

This should take care of making a Binarie<TType> different from a Binarie of a specific Type which have the same value.

Clearity

I've used a real lot of overloading functions within the source, this is done to give the feeling in a higher level to work with 1 function. Which should bring some clearity in the code. I also added many regions to block off a certain type of functionality.

Using the code

As you have read there are several ways to construct a Binarie or Binarie<TType>

Method One:

Binarie Value = new  Binarie("1111 1111");

This call will count the amount of bits supplied and determine the most fitting integer, the bits will be interpreted

as being unsigned. Possible valuetypes as result are: byte, ushort, uint, ulong, BigInteger.

Method Two:

Binarie Value = (Binarie)"1111 1111";

Method Two acts exactly as Method One.

Method Three:

Binarie Value = new Binarie("1111 1111", typeof(sbyte));

This call will convert the supplied value into supplied value type, if the literal is to big the function will raise an error.

Method Four:

Binarie Value = (Binarie<sbyte>)"1111 1111";

Here actually two things happen firstly there will be a Binarie<sbyte> created, secondly this object will be converted to Binarie. The rules applying are the same as with method three.

Exchangability

I wanted to be able to cast to and from Binarie and between Binarie<valuetype> Binarie<othervaluetype>

This has become possible with a lot of hand out written if's casts and some other small tricks. How this works can be read in the code.

Practically it means you can write

byte Value = (Binarie<int>)"1111";

or more extreme:

byte ByteTest = (Binarie)(Binarie<float>)(Binarie<byte>)(string)(Binarie<byte>)(Binarie)"1111 1111 1111 1111";

What's happening here above:

First we make a Binarie of type ushort, it's converted to a Binarie<byte> from this we get the binary literal string, which we use to make another Binarie<byte>, which on it's turn it's converted to a Binarie<float>. The last is converted to a normal Binarie, which on ends get converted into a byte.

I hope this extreme example demonstrates what can be done.

Challenge

When i was developing above code, i ran into a challenge, a logical restriction between base and child class.

U cannot write an operator which casts from base to child class. Though i still wanted this possibility, at least it should give the feeling u could. By having the generic version of Binarie doing all the real work in Binarie itself and keep it as a internal variable, which gives us the possibility to easily 'cast' to our base form. The other way around has a little more work to it, if the underlying value types differ, conversions and recalculation of the binary literal has to take place, which it does.

To make it still possible to use the Binarie and the Binarie<TType> together in one array without extra casting i implemented an interface on both.

public interface IBinarie
{
    string      ToString();
    IBinarie    FromString(string value);

    Type        TypeOfValue { get; }
    object      Value       { get; }
}

ToString results in a Binary Literal string.

FromString will create a new Binarie or Binarie<TType>. The supplied binary literal string must concur with the amount of bytes of TypeOfValue, if the amount of bits is to high an error will be thrown.

TypeOfValue supplies the Type.

Value supplies our cached real value which the Binarie represents. The Value on Binarie<TType> is implemented explicitly and added a TType Value { get; } to make working with it easier.

All together this should be enough to work polymorphic with Binarie and any Binarie<TType> instance.

Points of Interest

During making this little framework i learned how to convert floating point value types into integer value types and back again, i never had had the need before this project yet.

History

Second Release: Added the total How The Code Works and altered some texts.

First Release.

P.S.

The code is delivered as an assembly used by an windows form, if you go into the forn_load u can debug some examples.

License

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


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --