在VB.NET中生成随机整数

65

我需要生成一个介于 1 到 n 之间(其中 n 是一个正整数)的随机整数,用于进行单元测试。我不需要过于复杂的东西来确保真正的随机性 - 只是一个传统的随机数即可。

我该如何做?

12个回答

84

正如多次指出的那样,建议编写此类代码是有问题的:

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
我认为这实际上永远不会生成“100”。它实际上是在最小值和最大值之间(我认为)。 - ChatGPT
2
@maxhodges:是的,我认为你说得对。单词“between”中存在不幸的歧义;我不知道OP是否关心100是否被包括在内。个人而言,我并没有考虑这一点;我的答案只是旨在说明如何使用“Static”关键字在多个函数调用之间共享一个“Random”对象。 - Dan Tao
4
如果你想要一个 bug,那么请使用这段代码。微软公司设计了一个奇怪的 Next() 方法。其中 Min 参数是包含的最小值,正如人们所期望的一样,但 Max 参数是不包含的最大值,这是人们所不期望的。换句话说,如果你传入 min=1 和 max=5,则生成的随机数将是 1、2、3 或 4 中的任意一个,但永远不会包括 5。 - Shawn Kovac
4
我理解你会觉得有些惊讶。但我认为称其为“buggy”(有缺陷的)有点过分了;它只是不符合的预期行为而已。事实上,大多数随机数生成器的实现(据我所知)都是这样的:包括下界,不包括上界。在很多常见的使用情况下,这种方式非常方便,比如从数组中选择一个随机项(在选择0到array.length之间的项)。 - Dan Tao
1
谢谢 Dan。我很感激那个反馈。这很尴尬,然后我看到程序员认为“最小值”和“最大值”具有一致的相似性,就像是包容和排斥的差别…是的,我也是这么认为的。但这是人性。编程中有许多“怪癖”之所以编程如此棘手。我们只需要写更好的代码。我很感谢你的反馈。谢谢。 - Shawn Kovac
显示剩余11条评论

67

要获取一个介于1和N(含)之间的随机整数值,可以使用以下代码。

CInt(Math.Ceiling(Rnd() * n)) + 1

7
“between 1 and N (inclusive)”翻译错误,应返回一个介于1N之间(包括1N)的值。“Math.Ceiling(0)” 的结果为 0 - Qtax
5
Rnd() 函数可能返回0。如果发生这种情况,即使 n > 0,结果也将为 0。因此,这可能会导致非常严重的程序错误,特别是由于它如此罕见。如果你想要有 bug 的代码,请使用它。MS 文档中写道:“Rnd 函数返回小于 1 但大于等于零的值。”http://msdn.microsoft.com/en-us/library/f7s023d2(v=vs.90).aspx - Shawn Kovac
8
按原样尝试了一下,使用n=11时出现12的情况。不包括。MSDN有更好的例子:randomValue = CInt(Math.Floor((upperbound - lowerbound + 1) * Rnd())) + lowerbound。 - eric1825
Rnd() 是属于 [0;1[ 范围内的数字。如果你想要一个属于 ]0;1] 范围内的数字,那么可以使用 1-Rnd() - Ama

37

使用System.Random

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)

7
使用return New Random().Next(minValue,maxValue)看起来更简单。 - Ignacio Soler Garcia
2
如果用户想要一系列随机数而不是一个,那么他们需要保留随机数生成器的引用。 - Joseph Sturtevant
1
将“Dim Generator”更改为“Static Generator”,你就可以拥有一个实例(不是线程安全的,但在大多数实际情况下这并不重要)。 - Dan Tao
1
看起来更简单,但这段代码是错误的。如果你想要一个 bug,那就用这段代码吧。微软把他们的 Next() 方法设计得相当奇怪。Min 参数是包含的最小值,正如我们所期望的那样,但 Max 参数是排除的最小值,这是我们不期望的。换句话说,如果你传入 min=1 和 max=5,那么你的随机数将是 1、2、3 或 4 中的任意一个,但它永远不会包括 5。 - Shawn Kovac
@ShawnKovac - 很好的发现。我没有注意到Random.Next的最小值和最大值参数之间的包含/排除不匹配。代码示例已更新。 - Joseph Sturtevant

5

4
到目前为止,所有的答案都存在问题或漏洞(复数形式,不只是一个)。我将解释一下。但首先我想称赞丹·陶的眼光,他使用静态变量记住生成器变量,这样调用它多次就不会重复相同的 #,而且他还给出了非常好的解释。但他的代码有和其他大多数人一样的缺陷,如下所述。
微软使它们的Next()方法相当奇怪。最小参数是包含的最小值,正如我们期望的那样,但最大参数是排除的最大值,这是我们不期望的。换句话说,如果你传递min=1和max=5,那么你的随机数将是1、2、3或4中的任何一个,但它绝不会包括5。这是使用 Microsoft 的 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 的大小,而且打字更短,但是你自己选择吧。
我看到这种方法有两个潜在问题,但对大多数用途来说,它是合适(也正确的)。所以如果你想要一个简单的解决方案,我认为这是正确的。
我看到这个函数只有两个问题: 1:当 Max = Int32.MaxValue 时,加 1 会导致数字溢出。尽管这可能很少见,但仍然可能发生。 2:当 min > max + 1 时。当 min = 10 和 max = 5 时,Next 函数会抛出错误。这可能是你想要的。但也可能不是。或者考虑当 min = 5 且 max = 4 时。通过添加 1,将 5 传递给 Next 方法,但它并不会抛出错误,实际上这是一个错误,但我测试过的 Microsoft .NET 代码返回了 5。所以当最大值等于最小值时,它实际上不是一种“排他性”的最大值。但是当 max < min 时,Random.Next() 函数会抛出 ArgumentOutOfRangeException。所以 Microsoft 的实现在这方面真的很不一致和有 bug。
你可能希望在 min > max 时简单地交换数字,这样就不会抛出错误,但这完全取决于所需的内容。如果你想在无效值时报错,则最好在我们代码中将 Microsoft 的排他性最大值(max + 1)等于最小值的情况下也抛出错误。
处理 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

2
完全是胡说八道。正如Bill所说,Next()的行为完全正常,而不是“相当奇怪”。 - Lightness Races in Orbit
1
公平地说,对于各种编程语言的内置函数来说,“正常”可能是很普遍的,但对于其他领域来说,这实际上是相当奇怪的。你上一次听到有人说1D6会给你一个1到7之间的数字是什么时候?对于那些没有仔细阅读语言文档并且不太熟悉RNG函数通常奇怪行为的人来说,他们可能会感到惊讶,并且不知道发生了什么。 - MichaelS
@MichaelS,你用“1D6”表示1到7之间的数字有点让我摸不着头脑,我不明白你的“D”是什么意思。但是关于Microsoft的Random.Next(min, max)方法如何工作,程序提供了1和7,该方法返回1到6之间的数字。所以我认为你的比喻略微不准确(除非你指的是其他荒谬的行为)。但如果你想引用Microsoft的话,我只能说你的小混淆是完全正常的,因为MS的做法没有任何逻辑意义(除了其他人指出的:它已经成为了新的“规范”)。 - Shawn Kovac
我只是简单地表达了一点逻辑,即在逻辑上更好地使用包含最小值和包含最大值以保持一致性,并拒绝已经开始的准“规范”。就这样。 :) - Shawn Kovac
1D6 意味着掷一颗六面骰子——这种骰子在许多桌游或桌面角色扮演游戏中使用。数字范围从1到6,包括1和6,人们会说“1到6”,而不是“1到7”,并假设您知道7不包括在内。您可以从生活的许多其他方面使用类似的比喻。如果我说一个棒球联盟是为12岁到14岁之间的孩子而设的,那么我将包括12岁和14岁。等等。我想不出在数学课外排除范围上限的实际例子。 - MichaelS
好的,@MichaelS,现在我明白了你的帖子,我看到你和我完全一致。你只是用另一种方式表达了相同的概念。很好的观点。 :) 感谢澄清。 - Shawn Kovac

4
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

2
如果你想要出现错误,那就使用这段代码。微软公司的Next()方法相当奇怪。Min参数是包含的最小值,正如人们所期望的那样,但Max参数是排除的最大值,这是人们所不期望的。换句话说,如果你传递min=1和max=5,那么你的随机数将是1、2、3或4中的任意一个,但它永远不会包括5。 - Shawn Kovac
5
大多数随机数生成器都是这样实现的。 - Bill the Lizard

2

您应该仅创建一次伪随机数生成器:

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函数的定义令人困惑。
在我看来,每次需要一个随机数就创建一个生成器是错误的;伪随机数并不是这样工作的。
首先,你会遇到初始化问题,这个问题已经在其他回复中讨论过了。如果你只初始化一次,就不会有这个问题。
其次,我不确定你能得到有效的随机数序列;相反,你得到的是多个不同序列的第一个数字的集合,这些序列基于计算机时间自动种子化。我不确定这些数字能够通过确认序列随机性的测试。

1
如果您正在使用 Joseph 的答案,这是一个很好的答案,而且您像这样连续运行它们:
dim i = GetRandom(1, 1715)
dim o = GetRandom(1, 1715)

然后,由于它处理调用的速度非常快,结果可能会一遍又一遍地返回相同的结果。这在2008年可能不是问题,但由于今天的处理器速度更快,该函数在进行第二次调用之前不允许系统时钟有足够的时间来改变。由于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

2
如果你想要一个 bug,那么使用这段代码。微软公司设计的 Next() 方法相当奇怪。Min 参数是包含的最小值,正如人们所期望的那样,但 Max 参数却是不包含的最大值,这是人们所意料不到的。换句话说,如果你传递 min=1 和 max=5,那么你的随机数将是 1、2、3 或 4 中的任何一个,但它永远不会包括 5。 - Shawn Kovac
@ShawnKovac 感谢您让我了解到.Next函数不返回最大数以及静态system.random对象的使用(尽管Min和Max这个名称可能不再适用,因为现在只是一个范围:A到B)。我进行了测试,两者的性能相同。此外,我使用整数而不是int32,因为它目前绑定到int32(使它们几乎相同),并且由于我在SQL中做了很多工作,我喜欢这种语法,并且不介意在我的“tab”之前键入“e”。(每个人都有自己的喜好。)~干杯 - Rogala
感谢您关心纠正您的代码。在这方面,您已经远远超过许多其他人。是的,据我所知,Int32和Integer产生相同的效果,完全相同。除了对于不知道“Integer”大小的人来说,Int32更清晰。这就是我使用Int32的最大原因。但我理解这完全是个人偏好的问题。 - Shawn Kovac
我也喜欢更短的Int32别名,而且是的,在智能感知方面,它们的输入大致相同。但我更喜欢更紧凑的阅读方式。但是当我编写C#代码时,为了其他人的清晰度和新手的查看方便,我仍然喜欢使用更具体的Int32而不是更短的'int'。但每个人有自己的喜好。 :) - Shawn Kovac
我只想指出,如果您的max = Integer.MaxValue,那么您的代码将会出现错误或抛出异常。当然,这通常不会有影响,但如果您想消除潜在的问题,可以查看我的答案,我提供了一个解决方案,以便在用户想要包括Integer.MaxValue时给出完整的随机Int范围。不幸的是,微软在实现我所谓的极端古怪的随机函数时,并没有使得编写非常健壮的代码变得容易。真正健壮的代码很难找到。:( - Shawn Kovac

0
Dim rnd As Random = New Random
rnd.Next(n)

0

仅供参考,VB NET对于RND和RANDOMIZE的函数定义(应该会给出与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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接