仓颉语言 std.random 随机数包实战指南:Random 类 API 全解与底层实现剖析
仓颉语言 std.random 随机数包实战指南:Random 类 API 全解与底层实现剖析
导读
std.random 是仓颉(Cangjie)编程语言标准库中提供伪随机数生成能力的核心包。本文以该包的官方文档为骨架,围绕 Random 类的全部公开 API(构造函数、seed 属性、整数/浮点/布尔/字节数组/高斯分布随机数生成方法)逐一展开,并结合当前仓库中 random.cj 与 native/random.c 的源码实现,说明其基于 Mersenne Twister(梅森旋转)算法的底层原理、种子来源、参数校验与异常行为。读完本文,你将能够在仓颉项目中正确选用随机数生成方法、利用固定种子复现随机序列,并清楚理解该包"非安全用途、非线程安全"的边界约束。
文档与源码位置:本文核心 API 说明以 random_package_classes.md 为基准,包级总览见 random_package_overview.md;实现代码位于 random.cj。
std.random 包概览
random 包提供生成伪随机数的能力,包内目前只有一个公开类:
| 类名 | 功能 |
|---|---|
| Random | 提供生成伪随机数的相关功能 |
从包结构看,该包的构建入口在 stdlib/libs/std/random/CMakeLists.txt,其通过 add_subdirectory(native) 将 C 语言 FFI 文件 random.c 编译为 cangjie-std-randomFFI 静态库,同时由 stdlib/libs/std/CMakeLists.txt 中的 add_subdirectory(random) 将 random.cj 纳入标准库整体构建。
使用前提
在仓颉源码中通过以下方式导入:
import std.random.*
class Random 类说明
Random 类的公开声明如下:
public class Random {
public init()
public init(seed: UInt64)
}
功能:提供生成伪随机数的相关功能。
两个必须牢记的使用限制
文档在类说明处给出了两条重要注意项,任何使用 std.random 的场景都应首先确认:
- 非安全用途:此随机数生成器使用伪随机数生成算法,不可用于密码学、令牌生成、密钥生成、验证码等安全敏感场景。从实现看,其算法是确定性的 Mersenne Twister(见下文),只要种子可被观测或推断,整个序列就可能被复现,因此不能充当 CSPRNG(密码学安全随机数生成器)。
- 非线程安全:此类的实例不支持并发访问。在多线程环境中同时调用同一个
Random实例的方法会导致未定义行为。原因从源码结构可以推断:Random内部持有可变状态mt(状态数组)、mti(当前索引)、nextGaussian(缓存的高斯值)等成员变量(见 random.cj),这些字段的更新没有加锁保护,同一实例被多线程并发推进状态会产生竞态。实践中应在每个线程内创建独立的Random实例。
从源码看算法内核:Mersenne Twister
阅读 random.cj 可以看到完整的梅森旋转(Mersenne Twister)实现,这是该类"伪随机"特性的来源:
- 周期参数:
N = 0x138(312)、M = 0x9C(156)、MATRIX = 0xB5026F5AA96619E9,以及UPPER_MASK、LOWER_MASK(见 random.cj),对应 MT19937-64 变体的标准常量。 - Tempering 参数:
TEMPERING_MASK_A/B/C(见 random.cj)用于输出时的"温度处理"(tempering),即对状态值做多层移位异或,改善输出分布。 - 状态初始化:
init(seed)时以种子填充mt<a href="https://link.gitcode.com/i/5db3587648fed7ee53584cc3f9cbae0d" target="_blank">0],随后initialMtArray用乘法同余方式递推填充整个状态数组,其中关键常量6364136223846793005正是 Mersenne Twister 标准初始化常量(源码注释也明确标注了这一点,见 [random.cj)。 - 状态推进:
nextBits中当mti >= N时对全部 312 个状态字执行一次"twist"递推(见 random.cj),随后returnNext完成 tempering 并取出指定位数(见 random.cj)。
也就是说,Random 输出的每一个值都是内部 312 个 64 位状态字经过线性递推与输出变换的结果,序列由种子唯一确定——这正是文档中"相同种子生成相同伪随机数列表"的底层原因。
构造与种子:init()、init(UInt64) 与 seed 属性
默认构造 init()
public init()
功能:默认无参构造函数创建新的 Random 对象。
示例:
import std.random.*
main(): Unit {
// 使用默认构造函数创建Random对象
let random = Random()
// 生成一些随机数验证对象创建成功
let value = random.nextInt32()
println("生成的随机数: ${value}")
}
可能的运行结果:
生成的随机数: 1234567890
种子来源:默认构造函数并非无种子,而是委托给 this(getSeed())(见 random.cj)。getSeed() 通过 @FastNative 外部函数 CJ_RANDOM_NanoNow() 取当前系统时间(纳秒级),其 C 实现位于 random.c:
extern int64_t CJ_RANDOM_NanoNow(void)
{
struct timespec nano = {0, 0};
(void)clock_gettime(CLOCK_REALTIME, &nano);
return (int64_t)(nano.tv_sec) * (int64_t)NANOSECOND + (int64_t)(nano.tv_nsec);
}
即通过 clock_gettime(CLOCK_REALTIME) 获取"秒 × 10^9 + 纳秒"的时间戳,再经 @OverflowWrapping 语义包装为 UInt64 作为种子。因此默认构造的两次调用通常产生不同序列,但极端情况下(同一纳秒内连续创建、或系统时间被调整)仍可能得到相同种子。
指定种子构造 init(UInt64)
public init(seed: UInt64)
功能:使用随机数种子创建新的 Random 对象。
参数:
seed: UInt64(UInt64 表示 64 位无符号整型,范围为 [0, 2^64 - 1]):随机数种子。如果设置相同随机种子,生成的伪随机数列表相同。
示例:
import std.random.*
main(): Unit {
// 使用相同种子创建两个Random对象
let seed: UInt64 = 12345
let random1 = Random(seed)
let random2 = Random(seed)
// 验证相同种子产生相同的随机数序列
let value1_1 = random1.nextInt32()
let value2_1 = random2.nextInt32()
println("random1的第一个随机数: ${value1_1}")
println("random2的第一个随机数: ${value2_1}")
}
可能的运行结果:
random1的第一个随机数: 1861434509
random2的第一个随机数: 1861434509
可复现性的实战价值:这一"同种子同序列"特性使 Random 非常适合单元测试、仿真、游戏种子地图等需要可复现随机结果的场景。源码中 init(seed) 完成种子保存、状态数组初始化、mti = N 初始化的完整流程(见 random.cj),保证两个同种子实例的内部状态完全一致,从而输出序列也完全一致。
只读属性 seed
public prop seed: UInt64
功能:获取随机数种子。
类型:UInt64
示例:
import std.random.*
main(): Unit {
// 使用默认构造函数创建Random对象
let random = Random()
let seed = random.seed
println("种子: ${seed}")
}
可能的运行结果:
种子: 1768631965497376828
从源码看,seed 属性只提供 get() 访问器,直接返回内部 _seed 字段(见 random.cj),因此它是只读的,创建后无法修改种子。
指定位长随机数:next(UInt64) 与 nextBits(UInt64)
next(UInt64)(已标记废弃)
public func next(bits: UInt64): UInt64
功能:生成一个用户指定位长的随机整数。
注意:未来版本即将废弃,使用
nextBits替代。
参数:
bits: UInt64:要生成的伪随机数的位数,取值范围 (0, 64]。
返回值:
UInt64:用户指定位长的伪随机数。
异常:
IllegalArgumentException(core 包异常类):如果bits等于 0,或大于 64(超过UInt64所能表示的长度),则抛出异常。
示例:
import std.random.*
main(): Unit {
let random = Random()
// 生成指定位长的随机数
let randomValue = random.next(10)
println("生成的10位随机数: ${randomValue}")
}
可能的运行结果:
生成的10位随机数: 960
源码中 next 带有 @Deprecated<a href="https://link.gitcode.com/i/5f896e5741c49983da44a4185d5c718e" target="_blank">"Use member function public func nextBits(bits: UInt64): UInt64 instead."] 注解,且只是简单转发给 nextBits(bits)(见 [random.cj),可见其仅为兼容旧代码保留,新代码应直接使用 nextBits。
nextBits(UInt64)(推荐用法)
public func nextBits(bits: UInt64): UInt64
功能:生成一个指定位长的随机整数。
参数:
bits: UInt64:要生成的伪随机数的位数,取值范围 (0, 64]。
返回值:
UInt64:生成的用户指定位长的伪随机数。
异常:
IllegalArgumentException:如果bits等于 0,或大于 64,超过UInt64所能表示的长度,则抛出异常。
示例:
import std.random.*
main(): Unit {
let random = Random()
// 生成指定位长的随机数
let randomValue = random.nextBits(10)
println("生成的10位随机数: ${randomValue}")
}
可能的运行结果:
生成的10位随机数: 512
实现细节:nextBits 先做参数校验(bits > 64 或 bits == 0 时抛出带具体参数的 IllegalArgumentException,见 random.cj),然后按需推进 MT 状态,最后调用 returnNext(bits)。returnNext 的收尾是 yy & (MAX_MASK >> (64 - bits)),即只保留低 bits 位,其余高位清零(见 random.cj)。因此:
nextBits(1)得到 0 或 1;nextBits(10)得到 [0, 2^10) 即 [0, 1023] 的整数;nextBits(64)得到完整的UInt64范围随机值。
所有无参数整数方法(nextInt8/16/32/64、nextUInt8/16/32/64)与浮点方法(nextFloat16/32/64)在源码层面都最终复用 nextBits。
布尔与字节数组:nextBool() 与 nextBytes()
nextBool()
public func nextBool(): Bool
功能:获取一个布尔类型的伪随机值。
返回值:
Bool:一个Bool类型的伪随机数。
示例:
import std.random.*
main() {
// 创建一个随机数生成器
let random = Random()
// 生成一个布尔值
let bool = random.nextBool()
println("结果: ${bool}")
return 0
}
可能的运行结果:
结果: false
源码实现为 nextBits(1) != 0(见 random.cj),即取 1 位随机位,为 0 返回 false,为 1 返回 true。
nextBytes(Array<Byte>):就地填充
public func nextBytes(bytes: Array<Byte>): Unit
功能:生成随机数替换入参数组中的每个元素。
参数:
示例:
import std.random.*
main(): Unit {
let random = Random()
// 先创建一个字节数组
let bytes = Array<UInt8>(5, repeat: 0)
println("调用nextBytes前: ${bytes}")
// 使用nextBytes重新填充数组
random.nextBytes(bytes)
println("调用nextBytes后: ${bytes}")
}
可能的运行结果:
调用nextBytes前: [0, 0, 0, 0, 0]
调用nextBytes后: [138, 254, 94, 118, 164]
源码中该方法遍历数组并将每个元素赋值为 nextUInt8() 的随机字节(见 random.cj),返回值为 Unit,即修改发生在调用方传入的数组上,无需接收返回值。
nextBytes(Int32):返回新数组
public func nextBytes(length: Int32): Array<Byte>
功能:生成指定长度的随机数数组。
参数:
length: Int32(Int32):生成的随机数数组长度,length大于 0。
返回值:
Array<Byte>:生成的随机数数组。
异常:
IllegalArgumentException:当参数length小于等于 0 时,抛出异常。
示例:
import std.random.*
main(): Unit {
let random = Random()
// 生成长度为5的随机字节数组
let bytes = random.nextBytes(5)
// 打印生成的随机字节
println("生成的随机字节: ${bytes}")
}
可能的运行结果:
生成的随机字节: [86, 205, 17, 142, 41]
源码先校验 length <= 0 抛出异常,再通过数组构造器 Array<Byte>(Int64(length), {_ => this.nextUInt8()}) 一次性生成指定长度的随机字节数组(见 random.cj)。两个重载的差异可总结为:
| 重载 | 返回值 | 语义 |
|---|---|---|
nextBytes(bytes: Array<Byte>) |
Unit |
就地填充调用方传入的数组 |
nextBytes(length: Int32) |
Array<Byte> |
新建指定长度数组并返回,length <= 0 抛异常 |
相关废弃方法:nextUInt8s(Array<UInt8>)
public func nextUInt8s(array: Array<UInt8>): Array<UInt8>
功能:生成随机数替换入参数组中的每个元素。
注意:未来版本即将废弃,使用
nextBytes替代。
参数:
array: Array<UInt8>:被替换的数组。
返回值:
Array<UInt8>:返回替换后的Array。
示例:
import std.random.*
main(): Unit {
let random = Random()
// 先创建一个字节数组
let bytes = Array<UInt8>(5, repeat: 0)
println("调用nextUInt8s前: ${bytes}")
// 使用nextUInt8s重新填充数组
let result = random.nextUInt8s(bytes)
println("调用nextUInt8s后: ${result}")
}
可能的运行结果:
调用nextUInt8s前: [0, 0, 0, 0, 0]
调用nextUInt8s后: [237, 114, 163, 155, 228]
源码中该方法同样被 @Deprecated 标注(见 random.cj),新代码一律改用 nextBytes。
浮点随机数:nextFloat16/32/64
三个方法分别生成 Float16、Float32、Float64 类型的伪随机数,范围均为 [0.0, 1.0)(含 0,不含 1)。
nextFloat16()
public func nextFloat16(): Float16
功能:获取一个 Float16 类型的伪随机数,其范围为 [0.0, 1.0)。
返回值:
Float16(Float16):一个Float16类型的伪随机数。
示例:
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个Float16类型的随机数 [0.0, 1.0)
let randomFloat16 = rng.nextFloat16()
println("生成的Float16随机数: ${randomFloat16}")
return 0
}
可能的运行结果:
生成的Float16随机数: 0.286133
nextFloat32()
public func nextFloat32(): Float32
功能:获取一个 Float32 类型的伪随机数,其范围为 [0.0, 1.0)。
返回值:
Float32:一个Float32类型的伪随机数。
示例:
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个Float32类型的随机数 [0.0, 1.0)
let randomFloat32 = rng.nextFloat32()
println("生成的Float32随机数: ${randomFloat32}")
return 0
}
可能的运行结果:
生成的Float32随机数: 0.010008
nextFloat64()
public func nextFloat64(): Float64
功能:获取一个 Float64 类型的伪随机数,其范围为 [0.0, 1.0)。
返回值:
Float64:一个Float64类型的伪随机数。
示例:
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个Float64类型的随机数 [0.0, 1.0)
let randomFloat64 = rng.nextFloat64()
println("生成的Float64随机数: ${randomFloat64}")
return 0
}
可能的运行结果:
生成的Float64随机数: 0.510989
实现差异(见 random.cj):
| 方法 | 实现 | 精度说明 |
|---|---|---|
nextFloat16() |
Float16(nextBits(11) / (1 << 11)) |
11 位随机精度 |
nextFloat32() |
Float32(nextBits(24) / (1 << 24)) |
24 位随机精度 |
nextFloat64() |
Float64(nextBits(53) / (1 << 53)) |
53 位随机精度(双精度尾数位数) |
三者都采用"取指定位数的随机整数,除以 2 的对应次幂"的方式,将整数均匀映射到 [0, 1) 区间,精度越高分布越细密。
高斯(正态)分布:nextGaussianFloat16/32/64
三个方法分别生成 Float16、Float32、Float64 类型、符合指定均值与标准差的高斯分布的随机数。均值(mean)是期望值,可解释为位置参数,决定分布的位置;标准差(sigma)可解释为尺度参数,决定分布的幅度。两个参数均带默认值:mean = 0.0、sigma = 1.0,即不传参时得到标准正态分布 N(0, 1)。
方法签名一览
public func nextGaussianFloat16(mean!: Float16 = 0.0, sigma!: Float16 = 1.0): Float16
public func nextGaussianFloat32(mean!: Float32 = 0.0, sigma!: Float32 = 1.0): Float32
public func nextGaussianFloat64(mean!: Float64 = 0.0, sigma!: Float64 = 1.0): Float64
示例(以 Float64 为例)
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个Float64类型的高斯分布随机数(均值0.0,标准差1.0)
let gaussianFloat64 = rng.nextGaussianFloat64()
println("生成的默认高斯分布Float64随机数: ${gaussianFloat64}")
// 生成一个Float64类型的高斯分布随机数(均值2.0,标准差1.5)
let gaussianFloat64Custom = rng.nextGaussianFloat64(mean: 2.0, sigma: 1.5)
println("生成的自定义高斯分布Float64随机数: ${gaussianFloat64Custom}")
return 0
}
可能的运行结果:
生成的默认高斯分布Float64随机数: 0.461099
生成的自定义高斯分布Float64随机数: 3.610765
Float16、Float32 版本用法完全相同,仅类型不同(示例见 random_package_classes.md)。
底层算法:Box-Muller 变换
从源码看(见 random.cj),高斯随机数由私有的 gaussian() 方法基于 Box-Muller 变换生成:
R = sqrt(-2 * ln(V1)),Θ = 2 * π * V2
其中 V1 = 1.0 - nextFloat64(),V2 = nextFloat64()
一次计算同时产出 R * sin(Θ) 与 R * cos(Θ) 一对高斯值:返回值取其中一个,另一个缓存到 nextGaussian 字段(Option<Float64>),下一次调用时直接消费缓存、避免浪费计算。这一设计使得连续调用 nextGaussian* 时两个相邻输出来自同一次 Box-Muller 采样。
对外层方法而言,最终结果为 gaussian() * sigma + mean,即标准正态样本经缩放平移得到指定 N(mean, sigma^2) 分布。该组方法自 0.40.2 版本加入(源码 @since 0.40.2,见 random.cj)。
整数随机数:nextInt8/16/32/64 与 nextUInt8/16/32/64
无参数版本:全范围随机整数
无参数版本生成覆盖对应整数类型全部取值范围的伪随机数:
| 方法 | 返回类型 | 可能取值示例 |
|---|---|---|
nextInt8() |
Int8 |
2 |
nextInt16() |
Int16 |
23604 |
nextInt32() |
Int32 |
1556266486 |
nextInt64() |
Int64 |
-7311212217809863139 |
nextUInt8() |
UInt8 |
151 |
nextUInt16() |
UInt16 |
24058 |
nextUInt32() |
UInt32 |
738971091 |
nextUInt64() |
UInt64 |
9612265470820164117 |
以 nextInt32 为例的完整示例:
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个Int32类型的随机数
let randomInt32 = rng.nextInt32()
println("生成的Int32随机数: ${randomInt32}")
return 0
}
可能的运行结果:
生成的Int32随机数: 1556266486
源码实现:有符号整型方法通过 @OverflowWrapping 语义将 nextBits(8/16/32/64) 的结果强转为对应有符号类型(见 random.cj),因此 nextInt32() 可能返回负数;无符号版本则直接取 nextBits 的对应位数(见 random.cj)。
带范围版本:nextXxx(upper)
带一个 upper 参数的版本生成范围在 [0, upper) 的伪随机数,upper 本身不会被返回(左闭右开区间)。
| 方法 | upper 类型 |
取值范围 | 异常条件 |
|---|---|---|---|
nextInt8(upper) |
Int8 |
(0, Int8.Max] |
upper <= 0 |
nextInt16(upper) |
Int16 |
(0, Int16.Max] |
upper <= 0 |
nextInt32(upper) |
Int32 |
(0, Int32.Max] |
upper <= 0 |
nextInt64(upper) |
Int64 |
(0, Int64.Max] |
upper <= 0 |
nextUInt8(upper) |
UInt8 |
(0, UInt8.Max] |
upper == 0 |
nextUInt16(upper) |
UInt16 |
(0, UInt16.Max] |
upper == 0 |
nextUInt32(upper) |
UInt32 |
(0, UInt32.Max] |
upper == 0 |
nextUInt64(upper) |
UInt64 |
(0, UInt64.Max] |
upper == 0 |
(IntN.Max、UIntN.Max 为各整数类型的静态最大值属性,例如 UInt64.Max = 18446744073709551615。)
异常类型均为 IllegalArgumentException。
示例(nextInt32):
import std.random.*
main() {
// 创建一个Random实例
let rng = Random()
// 生成一个范围在 [0, 1000) 的Int32类型的随机数
let randomInt32Upper = rng.nextInt32(1000)
println("生成的范围在[0, 1000)的Int32随机数: ${randomInt32Upper}")
return 0
}
可能的运行结果:
生成的范围在[0, 1000)的Int32随机数: 571
其余类型的示例均与此同构,仅类型与上界数值不同,可参见 random_package_classes.md 中各小节(如 nextInt16(100) → [0, 100)、nextInt64(1000000) → [0, 1000000)、nextUInt32(1000000)、nextUInt64(1000000000000)、nextUInt8(100) 等)。
消除模偏差的算法细节:带范围版本并不是简单的 nextXxx() % upper。以 nextUInt32(upper) 为例(见 random.cj),其计算:
threshold = UInt32.Max - (UInt32.Max % upper + 1) % upper
r = nextUInt32()
while (r > threshold) { r = nextUInt32() } // 拒绝采样
return r % upper
即先求出"可接受的最大值" threshold,当原始随机数超过该值时拒绝并重新采样,从而避免因 Max 不能被 upper 整除导致的模偏差(modulo bias),保证 0, upper) 内每个值出现概率严格相等。有符号版本额外通过 next < 0 的条件丢弃负数(如 [random.cj),因为负数取模会得到负结果,不符合 [0, upper) 的语义。
使用注意事项与最佳实践
综合文档说明与源码实现,总结如下实践建议:
- 按场景选用构造方式:
- 需要"每次运行结果不同"(如抽样、游戏)→ 使用默认
Random(),种子来自系统实时时钟(纳秒级,见 random.c); - 需要"可复现"(如单元测试、回归验证)→ 使用
Random(seed)显式传入固定种子,相同种子产生完全相同序列。
- 需要"每次运行结果不同"(如抽样、游戏)→ 使用默认
- 范围随机优先用带参版本:不要自行用
nextInt32() % 100这类写法,应使用nextInt32(100)——它内置参数校验(非法upper抛IllegalArgumentException)与拒绝采样,结果无模偏差。 - 字节数据用 nextBytes:填充已有数组用
nextBytes(arr),新建数组用nextBytes(length);避免使用已废弃的next与nextUInt8s。 - 安全场景禁用:Mersenne Twister 是确定性 PRNG,种子可推断,不得用于密码学、令牌、密钥或验证码生成。
- 多线程各自持有实例:同一
Random实例跨线程共享调用是未定义行为;线程内各自new一个实例(可各用不同种子)。 - 仓库内延伸阅读:
- 标准库属性测试框架
std.unittest.prop_test中的RandomSource接口(random_source.cj)定义了与Random同构的随机数据源抽象(nextBool/nextInt8...nextFloat64/nextGaussianFloat64等),并以RandomDataProvider(random_strategy.cj)将其接入属性测试数据供给,说明Random类能力被标准库上层复用; Random类自 0.16.5 版本起提供(源码@since 0.16.5),高斯分布系列自 0.40.2 版本起提供;- 包级构建配置见 stdlib/libs/std/random/CMakeLists.txt,FFI 种子源实现见 stdlib/libs/std/random/native/random.c。
- 标准库属性测试框架
小结
std.random 以单个 Random 类覆盖了仓颉开发者日常所需的绝大多数伪随机数需求:通过 init()/init(seed) 两种构造方式控制随机源,通过 seed 属性读取种子,通过 nextBits、nextBool、nextBytes、nextFloat16/32/64、nextGaussianFloat16/32/64 以及 8 组整数 next*/next*(upper) 方法覆盖从位、布尔、字节数组到整数、浮点乃至高斯分布的完整取值谱系。其底层是标准 Mersenne Twister(MT19937-64 变体)实现,种子来源于系统纳秒时钟,范围版本采用拒绝采样消除模偏差,高斯版本基于 Box-Muller 变换并缓存配对样本。在使用中只需始终牢记两条红线——非安全用途与非线程安全——即可安全高效地驾驭该包的全部能力。