我需要生成一个介于 1 和 n 之间的随机整数(其中 n 是正整数)以用于单元测试。我不需要过于复杂的东西来确保真正的随机性——只需一个老式的随机数。
我该怎么做?
正如已经多次指出的那样,编写这样的代码的建议是有问题的:
Public Function GetRandom(ByVal Min As Integer, ByVal Max As Integer) As Integer
Dim Generator As System.Random = New System.Random()
Return Generator.Next(Min, Max)
End Function
原因是Random
该类的构造函数提供了基于系统时钟的默认种子。在大多数系统上,这具有有限的粒度——在 20 毫秒附近。因此,如果您编写以下代码,您将连续多次获得相同的数字:
Dim randoms(1000) As Integer
For i As Integer = 0 to randoms.Length - 1
randoms(i) = GetRandom(1, 100)
Next
下面的代码解决了这个问题:
Public Function GetRandom(ByVal Min As Integer, ByVal Max As Integer) As Integer
' by making Generator static, we preserve the same instance '
' (i.e., do not create new instances with the same seed over and over) '
' between calls '
Static Generator As System.Random = New System.Random()
Return Generator.Next(Min, Max)
End Function
我使用这两种方法编写了一个简单的程序来生成 1 到 100 之间的 25 个随机整数。这是输出:
Non-static: 70 Static: 70
Non-static: 70 Static: 46
Non-static: 70 Static: 58
Non-static: 70 Static: 19
Non-static: 70 Static: 79
Non-static: 70 Static: 24
Non-static: 70 Static: 14
Non-static: 70 Static: 46
Non-static: 70 Static: 82
Non-static: 70 Static: 31
Non-static: 70 Static: 25
Non-static: 70 Static: 8
Non-static: 70 Static: 76
Non-static: 70 Static: 74
Non-static: 70 Static: 84
Non-static: 70 Static: 39
Non-static: 70 Static: 30
Non-static: 70 Static: 55
Non-static: 70 Static: 49
Non-static: 70 Static: 21
Non-static: 70 Static: 99
Non-static: 70 Static: 15
Non-static: 70 Static: 83
Non-static: 70 Static: 26
Non-static: 70 Static: 16
Non-static: 70 Static: 75
要获得 1 和 N(含)之间的随机整数值,您可以使用以下命令。
CInt(Math.Ceiling(Rnd() * n)) + 1
Dim MyMin As Integer = 1, MyMax As Integer = 5, My1stRandomNumber As Integer, My2ndRandomNumber As Integer
' Create a random number generator
Dim Generator As System.Random = New System.Random()
' Get a random number >= MyMin and <= MyMax
My1stRandomNumber = Generator.Next(MyMin, MyMax + 1) ' Note: Next function returns numbers _less than_ max, so pass in max + 1 to include max as a possible value
' Get another random number (don't create a new generator, use the same one)
My2ndRandomNumber = Generator.Next(MyMin, MyMax + 1)
Microsoft 示例Rnd 函数
https://msdn.microsoft.com/en-us/library/f7s023d2%28v=vs.90%29.aspx
1-初始化随机数生成器。
Randomize()
2 - 生成 1 到 6 之间的随机值。
Dim value As Integer = CInt(Int((6 * Rnd()) + 1))
Public Function RandomNumber(ByVal n As Integer) As Integer
'initialize random number generator
Dim r As New Random(System.DateTime.Now.Millisecond)
Return r.Next(1, n)
End Function
到目前为止,所有答案都有问题或错误(复数,而不仅仅是一个)。我会解释。但首先我想赞扬 Dan Tao 的洞察力,即使用静态变量来记住 Generator 变量,因此多次调用它不会一遍又一遍地重复相同的 #,而且他给出了非常好的解释。但正如我现在解释的那样,他的代码遭受了与大多数其他人相同的缺陷。
MS 使他们的 Next() 方法相当奇怪。Min 参数是所期望的包含最小值,但 Max 参数是不期望的唯一最大值。换句话说,如果您传递 min=1 和 max=5,那么您的随机数将是 1、2、3 或 4 中的任何一个,但它永远不会包括 5。这是所有代码中两个潜在错误中的第一个使用微软的 Random.Next() 方法。
对于一个简单的答案(但仍然存在其他可能但罕见的问题),那么您需要使用:
Private Function GenRandomInt(min As Int32, max As Int32) As Int32
Static staticRandomGenerator As New System.Random
Return staticRandomGenerator.Next(min, max + 1)
End Function
(我喜欢使用,Int32
而不是Integer
因为它更清楚 int 有多大,而且打字更短,但适合你自己。)
我看到这种方法有两个潜在的问题,但它适用于(并且正确)大多数用途。因此,如果您想要一个简单的解决方案,我相信这是正确的。
我看到这个函数的唯一 2 个问题是: 1:当 Max = Int32.MaxValue 时,加 1 会产生数字溢出。尽管如此,这将是罕见的,它仍然是一种可能性。2:当 min > max + 1 时。当 min = 10 且 max = 5 时,Next 函数会引发错误。这可能是你想要的。但也可能不是。或者考虑当 min = 5 和 max = 4 时。通过添加 1,将 5 传递给 Next 方法,但它不会抛出错误,当它确实是错误时,但我测试的 Microsoft .NET 代码返回 5。所以当最大值 = 最小值时,它确实不是“独家”最大值。但是当 Random.Next() 函数的 max < min 时,它会抛出 ArgumentOutOfRangeException。所以微软的实现在这方面确实是不一致和错误的。
您可能想在 min > max 时简单地交换数字,以免引发错误,但这完全取决于所需的内容。如果您想在无效值上出错,那么当我们代码中 Microsoft 的专有最大值 (max + 1) 等于最小值时,最好也抛出错误,在这种情况下,MS 无法出错。
处理 max = Int32.MaxValue 时的解决方法有点不方便,但我希望发布一个处理这两种情况的彻底函数。如果您想要与我编码方式不同的行为,请适合自己。但要注意这两个问题。
快乐编码!
编辑:所以我需要一个随机整数生成器,我决定将它编码为“正确”。因此,如果有人想要完整的功能,这里有一个真正有效的。(但它并没有赢得只有 2 行代码的最简单的奖品。但它也不是很复杂。)
''' <summary>
''' Generates a random Integer with any (inclusive) minimum or (inclusive) maximum values, with full range of Int32 values.
''' </summary>
''' <param name="inMin">Inclusive Minimum value. Lowest possible return value.</param>
''' <param name="inMax">Inclusive Maximum value. Highest possible return value.</param>
''' <returns></returns>
''' <remarks></remarks>
Private Function GenRandomInt(inMin As Int32, inMax As Int32) As Int32
Static staticRandomGenerator As New System.Random
If inMin > inMax Then Dim t = inMin : inMin = inMax : inMax = t
If inMax < Int32.MaxValue Then Return staticRandomGenerator.Next(inMin, inMax + 1)
' now max = Int32.MaxValue, so we need to work around Microsoft's quirk of an exclusive max parameter.
If inMin > Int32.MinValue Then Return staticRandomGenerator.Next(inMin - 1, inMax) + 1 ' okay, this was the easy one.
' now min and max give full range of integer, but Random.Next() does not give us an option for the full range of integer.
' so we need to use Random.NextBytes() to give us 4 random bytes, then convert that to our random int.
Dim bytes(3) As Byte ' 4 bytes, 0 to 3
staticRandomGenerator.NextBytes(bytes) ' 4 random bytes
Return BitConverter.ToInt32(bytes, 0) ' return bytes converted to a random Int32
End Function
您应该只创建一次伪随机数生成器:
Dim Generator As System.Random = New System.Random()
然后,如果一个整数足以满足您的需要,您可以使用:
Public Function GetRandom(myGenerator As System.Random, ByVal Min As Integer, ByVal Max As Integer) As Integer
'min is inclusive, max is exclusive (dah!)
Return myGenerator.Next(Min, Max + 1)
End Function
你喜欢多少次。使用包装函数是合理的,只是因为最大值是独占的——我知道随机数是这样工作的,但是 .Next 的定义令人困惑。
在我看来,每次你需要一个数字时都创建一个生成器是错误的。伪随机数不能以这种方式工作。
首先,您会遇到其他回复中讨论过的初始化问题。如果你初始化一次,你就没有这个问题。
其次,我完全不确定您是否获得了有效的随机数序列;相反,您会得到多个不同序列的第一数量的集合,这些序列是根据计算机时间自动播种的。我不确定这些数字是否会通过确认序列随机性的测试。
如果您使用的是约瑟夫的答案,这是一个很好的答案,并且您像这样背靠背运行这些:
dim i = GetRandom(1, 1715)
dim o = GetRandom(1, 1715)
然后结果可能会一遍又一遍地返回,因为它处理呼叫的速度如此之快。这在 08 年可能不是问题,但由于今天的处理器速度要快得多,因此该函数不允许系统时钟有足够的时间在进行第二次调用之前进行更改。
由于 System.Random() 函数是基于系统时钟的,我们需要在下次调用之前留出足够的时间让它发生变化。实现此目的的一种方法是将当前线程暂停 1 毫秒。请参见下面的示例:
Public Function GetRandom(ByVal min as Integer, ByVal max as Integer) as Integer
Static staticRandomGenerator As New System.Random
max += 1
Return staticRandomGenerator.Next(If(min > max, max, min), If(min > max, min, max))
End Function
Dim rnd As Random = New Random
rnd.Next(n)
仅供参考,RND 和 RANDOMIZE 的 VB NET Fuction 定义(应该给出与 BASIC(1980 年)相同的结果以及之后的所有版本:
Public NotInheritable Class VBMath
' Methods
Private Shared Function GetTimer() As Single
Dim now As DateTime = DateTime.Now
Return CSng((((((60 * now.Hour) + now.Minute) * 60) + now.Second) + (CDbl(now.Millisecond) / 1000)))
End Function
Public Shared Sub Randomize()
Dim timer As Single = VBMath.GetTimer
Dim projectData As ProjectData = ProjectData.GetProjectData
Dim rndSeed As Integer = projectData.m_rndSeed
Dim num3 As Integer = BitConverter.ToInt32(BitConverter.GetBytes(timer), 0)
num3 = (((num3 And &HFFFF) Xor (num3 >> &H10)) << 8)
rndSeed = ((rndSeed And -16776961) Or num3)
projectData.m_rndSeed = rndSeed
End Sub
Public Shared Sub Randomize(ByVal Number As Double)
Dim num2 As Integer
Dim projectData As ProjectData = ProjectData.GetProjectData
Dim rndSeed As Integer = projectData.m_rndSeed
If BitConverter.IsLittleEndian Then
num2 = BitConverter.ToInt32(BitConverter.GetBytes(Number), 4)
Else
num2 = BitConverter.ToInt32(BitConverter.GetBytes(Number), 0)
End If
num2 = (((num2 And &HFFFF) Xor (num2 >> &H10)) << 8)
rndSeed = ((rndSeed And -16776961) Or num2)
projectData.m_rndSeed = rndSeed
End Sub
Public Shared Function Rnd() As Single
Return VBMath.Rnd(1!)
End Function
Public Shared Function Rnd(ByVal Number As Single) As Single
Dim projectData As ProjectData = ProjectData.GetProjectData
Dim rndSeed As Integer = projectData.m_rndSeed
If (Number <> 0) Then
If (Number < 0) Then
Dim num1 As UInt64 = (BitConverter.ToInt32(BitConverter.GetBytes(Number), 0) And &HFFFFFFFF)
rndSeed = CInt(((num1 + (num1 >> &H18)) And CULng(&HFFFFFF)))
End If
rndSeed = CInt((((rndSeed * &H43FD43FD) + &HC39EC3) And &HFFFFFF))
End If
projectData.m_rndSeed = rndSeed
Return (CSng(rndSeed) / 1.677722E+07!)
End Function
End Class
而随机类是:
Public Class Random
' Methods
<__DynamicallyInvokable> _
Public Sub New()
Me.New(Environment.TickCount)
End Sub
<__DynamicallyInvokable> _
Public Sub New(ByVal Seed As Integer)
Me.SeedArray = New Integer(&H38 - 1) {}
Dim num4 As Integer = If((Seed = -2147483648), &H7FFFFFFF, Math.Abs(Seed))
Dim num2 As Integer = (&H9A4EC86 - num4)
Me.SeedArray(&H37) = num2
Dim num3 As Integer = 1
Dim i As Integer
For i = 1 To &H37 - 1
Dim index As Integer = ((&H15 * i) Mod &H37)
Me.SeedArray(index) = num3
num3 = (num2 - num3)
If (num3 < 0) Then
num3 = (num3 + &H7FFFFFFF)
End If
num2 = Me.SeedArray(index)
Next i
Dim j As Integer
For j = 1 To 5 - 1
Dim k As Integer
For k = 1 To &H38 - 1
Me.SeedArray(k) = (Me.SeedArray(k) - Me.SeedArray((1 + ((k + 30) Mod &H37))))
If (Me.SeedArray(k) < 0) Then
Me.SeedArray(k) = (Me.SeedArray(k) + &H7FFFFFFF)
End If
Next k
Next j
Me.inext = 0
Me.inextp = &H15
Seed = 1
End Sub
Private Function GetSampleForLargeRange() As Double
Dim num As Integer = Me.InternalSample
If ((Me.InternalSample Mod 2) = 0) Then
num = -num
End If
Dim num2 As Double = num
num2 = (num2 + 2147483646)
Return (num2 / 4294967293)
End Function
Private Function InternalSample() As Integer
Dim inext As Integer = Me.inext
Dim inextp As Integer = Me.inextp
If (++inext >= &H38) Then
inext = 1
End If
If (++inextp >= &H38) Then
inextp = 1
End If
Dim num As Integer = (Me.SeedArray(inext) - Me.SeedArray(inextp))
If (num = &H7FFFFFFF) Then
num -= 1
End If
If (num < 0) Then
num = (num + &H7FFFFFFF)
End If
Me.SeedArray(inext) = num
Me.inext = inext
Me.inextp = inextp
Return num
End Function
<__DynamicallyInvokable> _
Public Overridable Function [Next]() As Integer
Return Me.InternalSample
End Function
<__DynamicallyInvokable> _
Public Overridable Function [Next](ByVal maxValue As Integer) As Integer
If (maxValue < 0) Then
Dim values As Object() = New Object() { "maxValue" }
Throw New ArgumentOutOfRangeException("maxValue", Environment.GetResourceString("ArgumentOutOfRange_MustBePositive", values))
End If
Return CInt((Me.Sample * maxValue))
End Function
<__DynamicallyInvokable> _
Public Overridable Function [Next](ByVal minValue As Integer, ByVal maxValue As Integer) As Integer
If (minValue > maxValue) Then
Dim values As Object() = New Object() { "minValue", "maxValue" }
Throw New ArgumentOutOfRangeException("minValue", Environment.GetResourceString("Argument_MinMaxValue", values))
End If
Dim num As Long = (maxValue - minValue)
If (num <= &H7FFFFFFF) Then
Return (CInt((Me.Sample * num)) + minValue)
End If
Return (CInt(CLng((Me.GetSampleForLargeRange * num))) + minValue)
End Function
<__DynamicallyInvokable> _
Public Overridable Sub NextBytes(ByVal buffer As Byte())
If (buffer Is Nothing) Then
Throw New ArgumentNullException("buffer")
End If
Dim i As Integer
For i = 0 To buffer.Length - 1
buffer(i) = CByte((Me.InternalSample Mod &H100))
Next i
End Sub
<__DynamicallyInvokable> _
Public Overridable Function NextDouble() As Double
Return Me.Sample
End Function
<__DynamicallyInvokable> _
Protected Overridable Function Sample() As Double
Return (Me.InternalSample * 4.6566128752457969E-10)
End Function
' Fields
Private inext As Integer
Private inextp As Integer
Private Const MBIG As Integer = &H7FFFFFFF
Private Const MSEED As Integer = &H9A4EC86
Private Const MZ As Integer = 0
Private SeedArray As Integer()
End Class
我看到很多用户对使用System.Random
.
尽管我个人仍然会使用System.Random
,但我正在考虑一种使用 GUID 作为随机值基础的方法。可以使用它的ToByteArray
方法将 GUID 转换为字节数组,并且可以使用BitConverter
.
'Function for reuse (min is inclusive and max is exclusive)
Function GetRandom(min As Integer, max As Integer) As Integer
Return BitConverter.ToUInt64(Guid.NewGuid.ToByteArray) Mod (max - min) + min
End Function
'one-liner specific for your purpose (n is exclusive)
BitConverter.ToUInt64(Guid.NewGuid.ToByteArray) Mod (n - 1) + 1
请注意,这只是一个小小的思想实验。我没有测试性能,也没有调查结果的实际“随机性”。但出于您的目的,它可能只是完成这项工作。
接受的答案使用该Microsoft.VisualBasic.VBMath.Rnd
方法,它确实提供了一个简单而有吸引力的单行器,但我个人会避免编写使用Microsoft.VisualBasic
命名空间的新代码。
Function xrand() As Long
Dim r1 As Long = Now.Day & Now.Month & Now.Year & Now.Hour & Now.Minute & Now.Second & Now.Millisecond
Dim RAND As Long = Math.Max(r1, r1 * 2)
Return RAND
End Function
[BBOYSE] 这是最好的方式,从头开始:P