Advanced literals for Visual Studio

As soon as I learned that Microsoft had opened a channel at uservoice.com so that we (developers) could suggest new/improved features for the upcoming Visual Studio 11 (now known as Visual Studio 2012), the first thing I wanted to suggest was the ability to easily handle numbers of different bases, natively.

Regardless of the language, which in this case is VB, consider the beauty and simplicity of the following code:

Dim decNum = 726d
Dim binNum = 1001b
Dim hexNum = f7a1h
Dim octNum = 167o

Here we are initializing several numeric variables which obtain their value based on a compiler automated procedure that converts the number into an unsigned integer from a base predefined by a suffix letter that conditions the conversion to the appropriate base type.

Then we would require simple ToXXX() functions (as well as “CType “, “DirectCast” or “as”, in C#, support) in order to easily and specifically convert from one type to another without requiring a string intermediate, as we do now… which, frankly, sucks!

For this to work, the Framework should implement distinct types for each base (of course, only for the most common ones) and derive them all from UInt (and make UInt inheritable) so that it’s easy to perform simple type castings between them and allows us to extend it to support any custom base.

In an attempt to overcome the limitations of the Framework in regards to its support for different bases I’ve been working on a class that should ease the process of working with binary values inside Visual Studio.
Nothing fancy… and nothing even close to what I’m proposing here but it definitely helps.

Here’s the code for the class:

Public Class Binary
    Public Enum Sizes
        Bit = 1
        Nibble = 4
        [Byte] = 8
        Word = 16
        DoubleWord = 32
        QuadWord = 64
        'DoubleQuadWord = 128
        Undefined = -1
    End Enum

    Private binaryValue As Long
    Private mSize As Sizes

    Public Sub New()
        mSize = Sizes.Word
    End Sub

    Public Sub New(value As Long, Optional size As Sizes = Sizes.Undefined)
        Me.New()
        binaryValue = Math.Abs(value)
        If size = Sizes.Undefined Then
            CalculateMinimumSize()
        Else
            mSize = size
            binaryValue = binaryValue And Mask(size)
        End If
    End Sub

    Public Sub New(value As String, Optional size As Sizes = Sizes.Undefined)
        Dim binValue As Binary = 0
        TryParse(value, binValue)
        binaryValue = binValue

        If size = Sizes.Undefined Then
            mSize = binValue.Size
        Else
            mSize = Sizes.Word
        End If
    End Sub

    Public Shared Function TryParse(value As String, ByRef result As Long) As Boolean
        Try
            Select Case value.Last()
                Case "d"
                    result = Long.Parse(value.TrimEnd("d"))
                    Return True
                Case "h"
                    result = Convert.ToInt32(value.TrimEnd("h"), 16)
                    Return True
                Case "b"
                    result = Convert.ToInt32(value.TrimEnd("b"), 2)
                    Return True
                Case "o"
                    result = Convert.ToInt32(value.TrimEnd("o"), 8)
                    Return True
                Case Else
                    Dim base As Integer = 2
                    For Each c In value
                        If c <> "0" AndAlso c <> "1" Then
                            If c >= "A" AndAlso c <= "F" Then
                                base = 16
                            ElseIf c < "0" OrElse c > "F" Then
                                base = -1
                                Exit For
                            ElseIf base <> 16 Then
                                base = 10
                            End If
                        End If
                    Next

                    If base = -1 Then
                        Return False
                    Else
                        result = Convert.ToInt32(value, base)
                        Return True
                    End If
            End Select
        Catch
            Return False
        End Try
    End Function

    Public Property Size As Sizes
        Get
            Return mSize
        End Get
        Set(value As Sizes)
            mSize = value
        End Set
    End Property

    Public Shared Narrowing Operator CType(value As Long) As Binary
        Return New Binary(value)
    End Operator

    Public Shared Narrowing Operator CType(value As Int32) As Binary
        Return New Binary(CUInt(Math.Abs(value)))
    End Operator

    Public Shared Narrowing Operator CType(value As String) As Binary
        Dim result As Binary = 0
        Binary.TryParse(value, result)
        Return result
    End Operator

    Public Shared Widening Operator CType(value As Binary) As Long
        Return value.ToLong()
    End Operator

    Public Shared Operator =(value1 As Binary, value2 As Binary) As Boolean
        Return value1.ToLong() = value2.ToLong()
    End Operator

    Public Shared Operator <>(value1 As Binary, value2 As Binary) As Boolean
        Return Not value1 = value2
    End Operator

    Public Shared Operator >(value1 As Binary, value2 As Binary) As Boolean
        Return value1.ToLong() > value2.ToLong()
    End Operator

    Public Shared Operator  value2
    End Operator

    Public Shared Operator >=(value1 As Binary, value2 As Binary) As Boolean
        Return value1.ToLong() >= value2.ToLong()
    End Operator

    Public Shared Operator = value2
    End Operator

    Public Shared Operator +(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() + value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator -(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() - value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator *(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() * value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator /(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() \ value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator \(value1 As Binary, value2 As Binary) As Binary
        Return value1 / value2
    End Operator

    Public Shared Operator ^(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() ^ value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator Mod(value1 As Binary, value2 As Binary) As Binary
        Return AdjustSize(value1.ToLong() Mod value2.ToLong(), value1.Size)
    End Operator

    Public Shared Operator And(value1 As Binary, value2 As Binary) As Binary
        Return value1.ToLong() And value2.ToLong()
    End Operator

    Public Shared Operator Or(value1 As Binary, value2 As Binary) As Binary
        Return value1.ToLong() Or value2.ToLong()
    End Operator

    Public Shared Operator Xor(value1 As Binary, value2 As Binary) As Binary
        Return value1.ToLong() Xor value2.ToLong()
    End Operator

    Public Shared Operator Not(value1 As Binary) As Binary
        Return AdjustSize(Not value1.ToLong(), value1.Size)
    End Operator

    Public Shared Operator <>(value1 As Binary, value2 As Integer) As Binary
        Return AdjustSize(value1.ToLong() >> value2, value1.Size)
    End Operator

    Public Shared Function From(value As String, Optional size As Sizes = Sizes.Undefined) As Binary
        Return New Binary(value, size)
    End Function

    Public Shared Function From(value As Long, Optional size As Sizes = Sizes.Undefined) As Binary
        Return New Binary(value, size)
    End Function

    Public Shared Function From(value As Int32, Optional size As Sizes = Sizes.Undefined) As Binary
        Return Binary.From(CUInt(Math.Abs(value)), size)
    End Function

    Private Shared Function AdjustSize(value As Long, size As Sizes) As Binary
        Return New Binary(value And Mask(size), size)
    End Function

    Private Shared Function Mask(size As Sizes) As Long
        Return (2 ^ size) - 1
    End Function

    Public Function ToLong() As Long
        Return binaryValue
    End Function

    Public Overrides Function ToString() As String
        Return ConvertToBase(2).PadLeft(mSize, "0")
    End Function

    Public Function ToHex() As String
        Return ConvertToBase(16)
    End Function

    Public Function ToOctal() As String
        Return ConvertToBase(8)
    End Function

    Private Sub CalculateMinimumSize()
        If binaryValue  0

                Return result
            End If
        End If
    End Function
End Class

The class is very basic and allows for the easy initialization of values of different bases.
It also provides a rudimentary support for basic arithmetic and logic operations between values instantiated with the class against native numerical data types.

It won’t server of any use in regards to performance or functionality. The whole and only purpose of the class is to let you write less awkward looking code so that it is easier to understand and maintain.

Attached is a ZIP file containing the Binary Class along with a simple test module.
Hope you find it useful!

UPDATE  (2012/Sept/9): I’ve changed the original Parse() function into a TryParse() function to easily detect when the parser is not able to identify the correct base for the value being passed.

BinaryClass (1936 downloads )