Convert Number to IP Address (v4) - vb.net

I'm trying to convert a four digit number into an IP Address. For example:
0001 ---> *.192.1.01
0011 ---> *.192.11.01
0111 ---> *.192.111.01
1111 ---> *.196.87.01
3458 ---> *.205.130.01
I believe the subnet mask is 255.255.192.0.
I would greatly appreciate any suggestions on the best way to do this in vb.net.
Other info:
This is for a simple pinger program where the user inputs a four digit number (the ID of the physical site that they wish to ping.) The IP Addressing scheme is simple, the 2nd & 3rd octet are used as the site number, and the fourth octet is used as the device at the site. I didn't design this scheme, hence why I'm unsure on how to get vb.net to understand it.
What I've Tried:
I thought about doing it the following way, which is extremely crude. However this would only work up to *.192.255.01 as I don't know how to split the number between the two octets once it goes over 255 in octet 3.
Private Sub btnStartPing(sender As Object, e As EventArgs) Handles btnStartPing.Click
Dim Octet1 As Integer = *
Dim Octet2 As Integer = 192
Dim Octet3 As Integer = txtSiteID.text
Dim Octet4 As Integer = 01
Dim CompleteIP As String = ""
CompletIP = Octet1 & "." & Octet2 & "." & Octet3 & "." & Octet4
'PING CompleteIP
end sub
Solution:
Dim var1 As Integer = Fix(192 + (NumericUpDown1.Value / 256))
Dim var2 As Integer = Fix((NumericUpDown1.Value Mod 256))
MsgBox("Your IP address is: " & "10." & var1 & "." & var2 & "." & "200")
End Sub

You'll need to split the number in two. Get the first half of the bits and add them to 192 and the second half of the bits are directly used.
Dim number As Integer
number = 1
Console.WriteLine("*.{0}.{1}.01", 192 + ((number And &HFF00) >> 8), number And &HFF) ' *.192.1.01
number = 11
Console.WriteLine("*.{0}.{1}.01", 192 + ((number And &HFF00) >> 8), number And &HFF) ' *.192.11.01
number = 111
Console.WriteLine("*.{0}.{1}.01", 192 + ((number And &HFF00) >> 8), number And &HFF) ' *.192.111.01
number = 1111
Console.WriteLine("*.{0}.{1}.01", 192 + ((number And &HFF00) >> 8), number And &HFF) ' *.196.87.01
number = 3458
Console.WriteLine("*.{0}.{1}.01", 192 + ((number And &HFF00) >> 8), number And &HFF) ' *.205.130.01
Take the number and split it in two
3458 = 0x0D82
0x0D
0x82
Then add 192 to the first part
0x0D + 192 = 205
0x82 = 130

This should make it
Public Function StartPing(txtSiteId As String) As String
Dim SiteId As Integer = Integer.Parse(txtSiteId)
'backslash performs integer division (no fractionary part)
'will throw an error when SiteId results in values greater than 255
'type Byte allows only values from 0 to 255
Dim Octet2 As Byte = 192 + (SiteId \ 256)
'Mod gets rest of division
Dim Octet3 As Byte = SiteId Mod 256
Return String.Format("*.{0}.{1}.01", Octet2, Octet3)
End Function

Related

VB.Net looping through a string

I have a string say "768932A3" that I want split up into two's like this:
76 89 32 A3 for purposes of conversion to binary. I have tried looping but it doesn't work. Kindly help:
For i = 0 To data.Length - 1
j = i + 2
incremented &= Convert.ToInt64(data.Substring(i, j), 16)
ascii &= Chr(Convert.ToInt64(data.Substring(i, j), 16))
i = j + 2
Next
The second parameter of Substring is not the ending position but the lenght of characters to consider from the starting position.
For i = 0 To data.Length - 1 Step 2
' Not clear what are you trying to do here
' incremented &= Convert.ToInt64(data.Substring(i, 2), 16)
Dim value As Long = Convert.ToInt64(data.Substring(i, 2), 16)
ascii &= Chr(Convert.ToInt64(data.Substring(i, 2), 16))
Next

How to set multiple bit values within an integer

I am coding a program in vb.net using Visual Studio 2015. I am trying to figure out how to modify individual bits in a 16 bit integer. The numeric data-type byte-order is little-Indian and is as follows:
origin (2 bits)
tagged (1 bit)
addressable (1 bit)
protocol (12 bits)
Field: [ origin ] [tagged] [addressable] [protocol]
Bits: 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
In the example code below, I am trying to figure out how to set origin, tagged, addressable and protocol in the variable "i" which is a 16 bit integer.
Dim i As UInt16 = 0
Dim origin As Byte = 0 ' Message origin indicator
Dim tagged As Byte = 0 ' Determines usage of the Frame Address target field (0 or 1)
Dim addressable As Byte = 0 ' Message includes a target address (0 or 1)
Dim protocol As UInt16 = 1024 ' Protocol number: must be 1024 (decimal)
Can anyone provide a vb.net example of how I can update the variable "i" so that it contains the bit values for origin, tagged, addressable and protocol?
You can use a combination of or to set individual bits and >> and << to shift bits.
For example, to set two bytes from tagged, addressable and protocol, you can do:
Dim tagged As Byte = 1 ' Determines usage of the Frame Address target field (0 or 1)
Dim addressable As Byte = 1 ' Message includes a target address (0 or 1)
Dim protocol As UInt16 = 1026 ' Protocol number: must be 1024 (decimal)
sendbuf(0) = sendbuf(0) or tagged ' bit 0
sendbuf(0) = sendbuf(0) or (addressable << 1) ' bit 1
sendbuf(0) = sendbuf(0) or ((protocol << 2) and 255) ' first 6 bits of protocol
sendbuf(1) = sendbuf(1) or (protocol >> 6) ' next 6 bits of protocol
You may need to adjust it - I didn't follow the link, so I'm not completely sure which bits should go where (there's 14 bits to fit into 2 bytes).
Map the bit values using binary values in an enumeration. 1 = 1, 2 = 10, 4 = 100, etc. Then you can assign your variable using the enumeration. You can also combine the values into a new enumeration (see ProtocolX).
To give initial values, just add the enumeration you want to use.
' switch bits on 1, 5, 13, 14, 16
i = FrameSectionEnum.ProtocolBit1 + FrameSectionEnum.ProtocolBit5 +
FrameSectionEnum.AddressableBit13 +
FrameSectionEnum.TaggedBit14 + FrameSectionEnum.OriginBit16
PrintBits(i)
To switch on some bits and preserve the other bits, use OR.
' switch bits on 2 and 3 using a combined value. preserve other bits
i = SetOn(i, FrameSectionEnum.ProtocolX)
PrintBits(i)
To switch off some bits and preserve the other bits, use AND and NOT.
' switch bits off 1 and 5
i = SetOff(i, FrameSectionEnum.ProtocolBit1 + FrameSectionEnum.ProtocolBit5)
PrintBits(i)
List of utilities functions:
Check if certain bits is on:
Function CheckBit(i As Integer, bit As FrameSectionEnum) As Integer
Return If((i And bit) = bit, 1, 0)
End Function
Set bits on, preserve other bits:
Function SetOn(i As Integer, bit As FrameSectionEnum) As Integer
Return i Or bit
End Function
Set bits off, preserve other bits:
Function SetOff(i As Integer, bit As FrameSectionEnum) As Integer
Return i And (Not bit)
End Function
Full code:
Module Module1
Enum FrameSectionEnum
ProtocolBit1 = 1
ProtocolBit2 = 2
ProtocolBit3 = 4
ProtocolBit4 = 8
ProtocolBit5 = 16
ProtocolBit6 = 32
ProtocolBit7 = 64
ProtocolBit8 = 128
ProtocolBit9 = 256
ProtocolBit10 = 512
ProtocolBit11 = 1024
ProtocolBit12 = 2048
AddressableBit13 = 4096
TaggedBit14 = 8192
OriginBit15 = 16384
OriginBit16 = 32768
ProtocolX = ProtocolBit2 + ProtocolBit3
End Enum
Sub Main()
Dim i As UInt16 = 0
' switch bits on 1, 5, 13, 14, 16
i = FrameSectionEnum.ProtocolBit1 + FrameSectionEnum.ProtocolBit5 +
FrameSectionEnum.AddressableBit13 +
FrameSectionEnum.TaggedBit14 + FrameSectionEnum.OriginBit16
PrintBits(i)
' switch bits on 2 and 3 using a combined value. preserve other bits
i = SetOn(i, FrameSectionEnum.ProtocolX)
PrintBits(i)
' switch bits off 1 and 5
i = SetOff(i, FrameSectionEnum.ProtocolBit1 + FrameSectionEnum.ProtocolBit5)
PrintBits(i)
Console.ReadKey(True)
End Sub
Function SetOn(i As Integer, bit As FrameSectionEnum) As Integer
Return i Or bit
End Function
Function SetOff(i As Integer, bit As FrameSectionEnum) As Integer
Return i And (Not bit)
End Function
Function CheckBit(i As Integer, bit As FrameSectionEnum) As Integer
Return If((i And bit) = bit, 1, 0)
End Function
Sub PrintBits(i As Integer)
Console.Write(CheckBit(i, FrameSectionEnum.OriginBit16))
Console.Write(CheckBit(i, FrameSectionEnum.OriginBit15))
Console.Write(CheckBit(i, FrameSectionEnum.TaggedBit14))
Console.Write(CheckBit(i, FrameSectionEnum.AddressableBit13))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit12))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit11))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit10))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit9))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit8))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit7))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit6))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit5))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit4))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit3))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit2))
Console.Write(CheckBit(i, FrameSectionEnum.ProtocolBit1))
Console.WriteLine()
End Sub
End Module
The header format in the question contains the key to getting all the bits in the right place. For me personally, it's easier to visualize if you renumber the bits using a zero-based index:
Field: [ origin ] [tagged] [addressable] [protocol]
Bits: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Starting with the origin field, it needs to be shifted 14 bits to the left, which can be done like this:
origin << 14
The tagged and addressable fields need to be shifted to the left by 13 and 12 bits, respectively, which can be done the same way. The protocol field is already in the right place, so it doesn't need to be shifted. They can all be combined together with the Or operator as follows:
i = (origin << 14) Or (tagged << 13) Or (addressable << 12) Or protocol
One last detail that needs to be addressed is that in VB.NET, bit shift operations are dependent upon the datatype that is being shifted. In your code above, the origin, tagged, and addressable variables are all of type Byte. Shifts on this type will all be done modulo 8 (the number of bits in a byte), meaning a shift of 9 bits is the same of a shift of 1 bit.
Because all of our shifts are more than 8 bits, we need to convert to a wider datatype or else things won't end up in the right place. The easy way to do this is to just change the declarations of all the variables to UInt16:
Dim i As UInt16
Dim origin As UInt16 = 0
Dim tagged As UInt16 = 0
Dim addressable As UInt16 = 0
Dim protocol As UInt16 = 1024
i = (origin << 14) Or (tagged << 13) Or (addressable << 12) Or protocol
The alternative to this is to keep your variables declared as they are and use CType to convert the fields right before doing the shifts:
Dim i As UInt16 = 0
Dim origin As Byte = 0
Dim tagged As Byte = 0
Dim addressable As Byte = 0
Dim protocol As UInt16 = 1024
i = (CType(origin, UInt16) << 14) Or (CType(tagged, UInt16) << 13) Or (CType(addressable, UInt16) << 12) Or protocol
Personally, I prefer the first way due to brevity, but the choice is yours!

How do I add probability to a random number generator

I'm trying to add probability to my random number generator.
Here is the random number generator code.
'Generate 3 random numbers'
Dim rn As New Random
Dim result1, result2, result3 As Integer
result1 = rn.Next(1, 12)
result2 = rn.Next(1, 12)
result3 = rn.Next(1, 12)
So this code will generate 3 random numbers and save them into separate variables.
I would like the following to happen.
1 has a 20% chance of being selected
2 has a 28% chance of being selected
values need to be between 1 and 12 and I need to select 3 numbers
Something along that line.
I found this but it seems to be a little off topic?
Could this be used for what I want?
Make an array with 100 items. Put the value 1 into 20 spots in the array, the value 2 into 28 spots in the array, as so on. Then pick a random array index. Depending on your values and probabilities you could simplify or adjust the size of the array.
Alternatively, you can just store boundary pairs ( {1, 20}, {2, 48}, ... ), grab a random number less than your highest boundary value, and find the number for the smallest pair where your boundary value is greater than or equal to the random result. Here's an example:
Private rnd As New Random()
Public Function GetValue() As Integer
'max value is 100
Dim boundaries = {
{20, 48, 56, 60, 69, 74, 77, 82, 84, 88, 92, 100},
{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}
}
Dim r As Integer = rnd.Next(1, 101) 'upper range is exclusive
For i As Integer = 0 To boundaries.GetUpperBound(1)
If boundaries(0, i) >= r Then Return boundaries(1, i)
Next
Throw New Exception() 'code should not be able to get here
End Function
Dim result1 As Integer = GetValue()
Dim result2 As Integer = GetValue()
Dim result3 As Integer = GetValue()
I while ago I was looking into generating a random Name list that mimicked he national USA population bases; the several thousand names needed mimicked the repetitive usage seen in the USA population. To do this I needed a list of First names along with Sur names and their frequency of usage. So I pulled the seed data from the Social Security Administration to obtain the top 1000 First and Sur names, along with their frequency of usage.
Think of three columns. The first column is a list of names, the second their frequency seen in the population bases, and the third is the rolling totals of their frequency added row to row:
Marry, 57, 10
John, 40, 60
Lloyd, 2, 62
Zac, 1, 61
Read a seed file of any size into an area, along with the weight value for each name (or number). This script assigns the rolling total weight values, then generates a random number between 1 and the sum of all weights. Checks that random number against the rolling sum of weights to locate the number associated with that weight.
Looking at the above example you have a 57% chance to generate Marry, and a 1% chance to generate Zac.
The below script is a more robust example of the general idea. It will generate random numbers between 1 – 25, based on the probability of their weights. The portion of the script that generates the random numbers between 1 – 25 loops 128 times to give you 128 numbers based on their probability of being selected.
I basically used something like this to generate thousands of random names to mimic a population bases that mirrored the USA.
Dim iVar001, iVar002, iVar003
Dim iMin, iMax, iRand, iRandN
Dim iRow, iCol
Dim aName
Dim aList()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' iMin is the lower range of random numbers, iMax is the upper
' range of random numbers being generated
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
iMin = 1
iMax = 25
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Create a dinamic sized 2D area used to generate a random number
' based on weighted Probability. Array values array(0, 0) threw
' array(x, 0), where “x” is the upper range value of the number
' to be generated, contains the range of random numbers you will
' be generating. Array values array(0, 1) to array(x, 1) contains
' the weight value for each number. Array values array(0,2) to
' array(x,2) contains the rolling sun of the weight values.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
ReDim aList(iMax - 1, 2)
iVar001 = 0
Do While iVar001 <= (iMax - 1)
Randomize
aList(iVar001, 0) = iVar001 + 1
aList(iVar001, 1) = Int(((20 * iMax) - iMin + 1)* Rnd + iMin)
If iVar001 = 0 Then
aList(iVar001, 2) = aList(iVar001, 1)
Else
aList(iVar001, 2) = aList(iVar001, 1) + aList(iVar001 - 1, 2)
End If
aName = aName & aList(iVar001, 0) & " - " & aList(iVar001, 1) & _
" - " & aList(iVar001, 2) & vbCrLf
iVar001 = iVar001 + 1
Loop
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Generate a message box containing the array values.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
MsgBox ("List of numbers," & vbCrLf& "weight values and" & _
vbCrLf & "totals of weights." & vbCrLf & vbCrLf & aName)
aName = ""
iCount000 = 1
iCount001 = 1
iCol001 = 0
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Generate a randon number between 1 and the total of weight
' values. Then step threw the array values until the the random
' value is nolonger less than or equal to the running wieght value,
' and record the number associated with that running weight value.
' The count of random =numbers generates is controled by the
' following Do While iCount000 <= 198.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ''
Do While iCount000 <= 198
Randomize
iRand = Int((aList(UBound(aList,1),UBound(aList,2)) - iMin + 1)* Rnd + iMin)
iVar001 = 0
Do While iRand >= aList(iVar001, 2)
iVar001 = iVar001 + 1
iRandN = aList(iVar001, 0)
Loop
iCount000 = iCount000 + 1
If iCount001 = "19" Then
aName = vbCrLf & aName
iCount001 = 0
Else
iCount001 = iCount001 + 1
End If
aName = iRandN & ", " & aName
Loop
MsgBox "List of random numbers in range " & iMin & " to " & iMax & "," & vbCrLf & _
"based on weighted probability." & vbCrLf & vbCrLf & aName

How to generate a 26-character hex string that equals to 106 bits and ((53 Ones - 53 Zeros) in binary)

I am looking for a way to generate a hexadecimal string that equals out to 106 bits, more specifically fifty three 1's and fifty three 0's after each hex char is converted to binary and added together. I'd like to keep it as random as possible considering the parameters of the request. How would I go about keeping an eye on the construction of the string so that it equals out the way I want?
For example:
(a8c05779f8934b14ce96f8aa93) =
(1010 1000 1100 0000 0101 0111 0111 1001 1111 1000 1001 0011 0100
1011 0001 0100 1100 1110 1001 0110 1111 1000 1010 1010 1001 0011)
One option is to create a list with an equal number of 0s and 1s and then sort it with an array of random keys:
Sub Main()
' Start with a list of 53 0's and 1's
Dim bitsList = New List(Of Integer)
For i = 1 To 53
bitsList.Add(1)
bitsList.Add(0)
Next
Dim bits = bitsList.ToArray()
' Create list of random keys
Dim keys = New List(Of Integer)
Dim rand = New Random()
For i = 1 To bits.Count
keys.Add(rand.Next())
Next
' Sort bits by random keys
Array.Sort(keys.ToArray(), bits)
' Create hex string
Dim s = ""
For i = 1 To bits.Length - 4 Step 4
Dim digit = bits(i + 3) * 8 + bits(i + 2) * 4 + bits(i + 1) * 2 + bits(i)
s = s + Hex(digit)
Next
Console.WriteLine(s)
End Sub
You can place 52 ones randomly in a 104 bit number by keeping track of how many ones has been placed already and calculate the probability that the next digit should be one. The first digit always has 1/2 probability (52/104), then the second digit has 51/103 or 52/103 probability depending on what the first digit was, and so on.
Put the bits in a buffer, and when it is full (four bits), that makes a hexadecimal digit that you can add to the string:
Dim rnd As New Random()
Dim bin As New StringBuilder()
Dim buf As Integer = 0, bufLen As Integer = 0, left As Integer = 52
For i As Integer = 104 To 1 Step -1
buf <<= 1
If rnd.Next(i) < left Then
buf += 1
left -= 1
End If
bufLen += 1
If bufLen = 4 Then
bin.Append("0123456789abcdef"(buf))
bufLen = 0
buf = 0
End If
Next
Dim b As String = bin.ToString()
To make a 106 bit value, change these lines:
Dim buf As Integer = 0, bufLen As Integer = 0, left As Integer = 53
For i As Integer = 106 To 1 Step -1
The resulting string is still 26 characters, the two extra bits are in the buf variable. It has a value between 0 and 3 that you can use to create the 27th character, however that is done.
To add a 22 bit hash to the string, you can use code like this:
bin.Append("048c"(buf))
Dim b As String = bin.ToString()
Dim m As New System.Security.Cryptography.SHA1Managed
Dim hash As Byte() = m.ComputeHash(Encoding.UTF8.GetBytes(b))
'replace first two bits in hash with bits from buf
hash(0) = CByte(hash(0) And &H3F Or (buf * 64))
'append 24 bits from hash
b = b.Substring(0, 26) + BitConverter.ToString(hash, 0, 3).Replace("-", String.Empty)

Generate random string in text field

We have that old software (made by one of the first employees many years ago) in company that uses Microsoft Access to run. Boss asked me to add a random string generation in the specific text box on click but i have no idea how to do that. I dont have any Microsoft Access programming experience, thats why i am askin you to help.
I managed to create button and text field so far. Thats where it stops. I also managed to access the code for the button action:
Private Sub command133_Click()
End Sub
This is one way, will work in Access VBA (which is an older basic than vb.net). It will generate a string with letters and numbers.
Sub test()
Dim s As String * 8 'fixed length string with 8 characters
Dim n As Integer
Dim ch As Integer 'the character
For n = 1 To Len(s) 'don't hardcode the length twice
Do
ch = Rnd() * 127 'This could be more efficient.
'48 is '0', 57 is '9', 65 is 'A', 90 is 'Z', 97 is 'a', 122 is 'z'.
Loop While ch < 48 Or ch > 57 And ch < 65 Or ch > 90 And ch < 97 Or ch > 122
Mid(s, n, 1) = Chr(ch) 'bit more efficient than concatenation
Next
Debug.Print s
End Sub
Try this function:
Public Function GetRandomString(ByVal iLength As Integer) As String
Dim sResult As String = ""
Dim rdm As New Random()
For i As Integer = 1 To iLength
sResult &= ChrW(rdm.Next(32, 126))
Next
Return sResult
End Function
Workin on #Bathsheba code, I did this. It will generate a random string with the number of characters you'd like.
Code :
Public Function GenerateUniqueSequence(numberOfCharacters As Integer) As String
Dim random As String ' * 8 'fixed length string with 8 characters
Dim j As Integer
Dim ch As Integer ' each character
random = ""
For j = 1 To numberOfCharacters
random = random & GenerateRandomAlphaNumericCharacter
Next
GenerateUniqueSequence = random
End Function
Public Function GenerateRandomAlphaNumericCharacter() As String
'Numbers : 48 is '0', 57 is '9'
'LETTERS : 65 is 'A', 90 is 'Z'
'letters : 97 is 'a', 122 is 'z'
GenerateRandomAlphaNumericCharacter = ""
Dim i As Integer
Randomize
i = (Rnd() * 2) + 1 'One chance out of 3 to choose one of 3 catégories
Randomize
Select Case i
Case 1 'Numbers
GenerateRandomAlphaNumericCharacter = Chr(Rnd() * 9 + 48)
Case 2 'LETTERS
GenerateRandomAlphaNumericCharacter = Chr(Rnd() * 25 + 65)
Case 3 'letters
GenerateRandomAlphaNumericCharacter = Chr(Rnd() * 25 + 97)
End Select
End Function
I use it with random number of characters, like this :
'Generates random Session ID between 15 and 30 alphanumeric characters
SessionID = GenerateUniqueSequence(Rnd * 15 + 15)
Result :
s8a8qWOmoDvC4jKRjPr5hOY12u 26
TB24qZ4cNfr6EdyY0J 18
6LZRQ9P5WHLNd71LIdqJ 20
KPN0RmlhhJKnVzPTkW 18
R2pNOKWJMKl9KpSoIV2egUNTEb1QC2 30
X8jHuupP6SvEI8Dt2wJi 20
NOTE: This is still not completely random. It will give a higher count of numbers than normal as approx 1/3 of all chars generated will be numbers.
Normally distribution will look like:
10 numbers plus 26 lowercase plus 26 uppercase = 62 possible chars.
Numbers will normally be 10/62 parts of the string or 1/6.2
With the code
i = (Rnd() * 2) + 1 'One chance out of 3 to choose one of 3 catégories
the count of numbers is pushed up to 1/3 (on average)
Probably not too much of a worry - unless you are trying to beat the NSA and then you have decreased your range significantly.