authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-14 19:10:00+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-14 19:10:00+01:00
logb99dc12ecece60caecf05d4b41495f6979f944d0
treec4db6f5238e0a930012a3275258c9f7aca923282
parent9d1a39c50ffeca1e42ed5f1b75a05d92eec869db
parente10a325410f075982605ae9be664a6e95213223f

Merge pull request 'libc: use common `rand` and `*rand48` implementations' (#30826) from GasInfinity/zig:libc-rand into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30826 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

18 files changed, 209 insertions(+), 140 deletions(-)

lib/c/stdlib.zig+3
...@@ -7,6 +7,9 @@ const ldiv_t = std.c.ldiv_t;...@@ -7,6 +7,9 @@ const ldiv_t = std.c.ldiv_t;
7const lldiv_t = std.c.lldiv_t;7const lldiv_t = std.c.lldiv_t;
88
9comptime {9comptime {
10 _ = @import("stdlib/rand.zig");
11 _ = @import("stdlib/drand48.zig");
12
10 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {13 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
11 // Functions specific to musl and wasi-libc.14 // Functions specific to musl and wasi-libc.
12 @export(&abs, .{ .name = "abs", .linkage = common.linkage, .visibility = common.visibility });15 @export(&abs, .{ .name = "abs", .linkage = common.linkage, .visibility = common.visibility });
lib/c/stdlib/drand48.zig created+147
...@@ -0,0 +1,147 @@
1//! drand48 functions are based off a 48-bit lcg prng: https://pubs.opengroup.org/onlinepubs/9799919799/functions/drand48.html
2
3const std = @import("std");
4const common = @import("../common.zig");
5const builtin = @import("builtin");
6const Lcg = std.Random.lcg.Wrapping(u48);
7
8comptime {
9 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
10 @export(&erand48, .{ .name = "erand48", .linkage = common.linkage, .visibility = common.visibility });
11 @export(&jrand48, .{ .name = "jrand48", .linkage = common.linkage, .visibility = common.visibility });
12 @export(&nrand48, .{ .name = "nrand48", .linkage = common.linkage, .visibility = common.visibility });
13 @export(&drand48, .{ .name = "drand48", .linkage = common.linkage, .visibility = common.visibility });
14 @export(&lrand48, .{ .name = "lrand48", .linkage = common.linkage, .visibility = common.visibility });
15 @export(&mrand48, .{ .name = "mrand48", .linkage = common.linkage, .visibility = common.visibility });
16 @export(&lcong48, .{ .name = "lcong48", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&seed48, .{ .name = "seed48", .linkage = common.linkage, .visibility = common.visibility });
18 @export(&srand48, .{ .name = "srand48", .linkage = common.linkage, .visibility = common.visibility });
19 }
20}
21
22// NOTE: all "magic" numbers and tests are extracted and adapted from the source above
23
24const default_multiplier = 0x5DEECE66D;
25const default_addend = 0xB;
26
27var lcg: Lcg = .init(0, default_multiplier, default_addend);
28var seed48_xi: [3]c_ushort = undefined;
29
30fn erand48(xsubi: *[3]c_ushort) callconv(.c) f64 {
31 const xi = @as(u48, @as(u16, @truncate(xsubi[0])) | (@as(u48, @as(u16, @truncate(xsubi[1])))) << 16) | (@as(u48, @as(u16, @truncate(xsubi[2]))) << 32);
32
33 var separate_lcg: Lcg = .init(xi, lcg.a, lcg.c);
34 const next_xi = separate_lcg.next();
35
36 xsubi.* = .{ @truncate(next_xi & 0xFFFF), @truncate((next_xi >> 16) & 0xFFFF), @truncate((next_xi >> 32) & 0xFFFF) };
37 return @as(f64, next_xi) / @as(f64, std.math.maxInt(u48));
38}
39
40fn jrand48(xsubi: *[3]c_ushort) callconv(.c) c_long {
41 const xi = @as(u48, @as(u16, @truncate(xsubi[0])) | (@as(u48, @as(u16, @truncate(xsubi[1])))) << 16) | (@as(u48, @as(u16, @truncate(xsubi[2]))) << 32);
42
43 var separate_lcg: Lcg = .init(xi, lcg.a, lcg.c);
44 const next_xi = separate_lcg.next();
45
46 xsubi.* = .{ @truncate(next_xi & 0xFFFF), @truncate((next_xi >> 16) & 0xFFFF), @truncate((next_xi >> 32) & 0xFFFF) };
47 return @as(i32, @bitCast(@as(u32, @truncate(next_xi >> 16))));
48}
49
50fn nrand48(xsubi: *[3]c_ushort) callconv(.c) c_long {
51 const xi = @as(u48, @as(u16, @truncate(xsubi[0])) | (@as(u48, @as(u16, @truncate(xsubi[1])))) << 16) | (@as(u48, @as(u16, @truncate(xsubi[2]))) << 32);
52
53 var separate_lcg: Lcg = .init(xi, lcg.a, lcg.c);
54 const next_xi = separate_lcg.next();
55
56 xsubi.* = .{ @truncate(next_xi & 0xFFFF), @truncate((next_xi >> 16) & 0xFFFF), @truncate((next_xi >> 32) & 0xFFFF) };
57 return @intCast(next_xi >> 17); // a c_long is always at least 32-bits, this is never UB
58}
59
60fn drand48() callconv(.c) f64 {
61 return 2e-48 * @as(f64, lcg.next());
62}
63
64fn lrand48() callconv(.c) c_long {
65 return @intCast(lcg.next() >> 17);
66}
67
68fn mrand48() callconv(.c) c_long {
69 return @as(i32, @bitCast(@as(u32, @truncate(lcg.next() >> 16))));
70}
71
72// 0..3 is `Xi`, 3..6 is `a`, 6 is `c`
73// first low 16-bits, then mid, then high.
74fn lcong48(param: *[7]c_ushort) callconv(.c) void {
75 lcg.xi = (@as(u48, @as(u16, @truncate(param[0]))) | (@as(u48, @as(u16, @truncate(param[1])))) << 16) | (@as(u48, @as(u16, @truncate(param[2]))) << 32);
76 lcg.a = (@as(u48, @as(u16, @truncate(param[3]))) | (@as(u48, @as(u16, @truncate(param[4])))) << 16) | (@as(u48, @as(u16, @truncate(param[5]))) << 32);
77 lcg.c = @as(u16, @truncate(param[6]));
78}
79
80fn seed48(seed16v: *[3]c_ushort) callconv(.c) *[3]c_ushort {
81 seed48_xi = .{ @truncate(lcg.xi & 0xFFFF), @truncate((lcg.xi >> 16) & 0xFFFF), @truncate((lcg.xi >> 32) & 0xFFFF) };
82 const xi = (@as(u48, @as(u16, @truncate(seed16v[0]))) | (@as(u48, @as(u16, @truncate(seed16v[1])))) << 16) | (@as(u48, @as(u16, @truncate(seed16v[2]))) << 32);
83 lcg = .init(xi, default_multiplier, default_addend);
84 return &seed48_xi;
85}
86
87fn srand48(seedval: c_long) callconv(.c) void {
88 const xi = (@as(u32, @truncate(@as(c_ulong, @bitCast(seedval)))) << 16) | 0x330E;
89 lcg = .init(xi, default_multiplier, default_addend);
90}
91
92test erand48 {
93 var xsubi: [3]c_ushort = .{ 37174, 64810, 11603 };
94
95 try std.testing.expectApproxEqAbs(0.8965, erand48(&xsubi), 0.0005);
96 try std.testing.expectEqualSlices(c_ushort, &.{ 22537, 47966, 58735 }, &xsubi);
97
98 try std.testing.expectApproxEqAbs(0.3375, erand48(&xsubi), 0.0005);
99 try std.testing.expectEqualSlices(c_ushort, &.{ 37344, 32911, 22119 }, &xsubi);
100
101 try std.testing.expectApproxEqAbs(0.6475, erand48(&xsubi), 0.0005);
102 try std.testing.expectEqualSlices(c_ushort, &.{ 23659, 29872, 42445 }, &xsubi);
103
104 try std.testing.expectApproxEqAbs(0.5005, erand48(&xsubi), 0.0005);
105 try std.testing.expectEqualSlices(c_ushort, &.{ 31642, 7875, 32802 }, &xsubi);
106
107 try std.testing.expectApproxEqAbs(0.5065, erand48(&xsubi), 0.0005);
108 try std.testing.expectEqualSlices(c_ushort, &.{ 64669, 14399, 33170 }, &xsubi);
109}
110
111test jrand48 {
112 var xsubi: [3]c_ushort = .{ 25175, 11052, 45015 };
113
114 try std.testing.expectEqual(1699503220, jrand48(&xsubi));
115 try std.testing.expectEqualSlices(c_ushort, &.{ 2326, 23668, 25932 }, &xsubi);
116
117 try std.testing.expectEqual(-992276007, jrand48(&xsubi));
118 try std.testing.expectEqualSlices(c_ushort, &.{ 41577, 4569, 50395 }, &xsubi);
119
120 try std.testing.expectEqual(-19535776, jrand48(&xsubi));
121 try std.testing.expectEqualSlices(c_ushort, &.{ 31936, 59488, 65237 }, &xsubi);
122
123 try std.testing.expectEqual(79438377, jrand48(&xsubi));
124 try std.testing.expectEqualSlices(c_ushort, &.{ 40395, 8745, 1212 }, &xsubi);
125
126 try std.testing.expectEqual(-1258917728, jrand48(&xsubi));
127 try std.testing.expectEqualSlices(c_ushort, &.{ 37242, 28832, 46326 }, &xsubi);
128}
129
130test nrand48 {
131 var xsubi: [3]c_ushort = .{ 546, 33817, 23389 };
132
133 try std.testing.expectEqual(914920692, nrand48(&xsubi));
134 try std.testing.expectEqualSlices(c_ushort, &.{ 29829, 10728, 27921 }, &xsubi);
135
136 try std.testing.expectEqual(754104482, nrand48(&xsubi));
137 try std.testing.expectEqualSlices(c_ushort, &.{ 6828, 28997, 23013 }, &xsubi);
138
139 try std.testing.expectEqual(609453945, nrand48(&xsubi));
140 try std.testing.expectEqualSlices(c_ushort, &.{ 58183, 3826, 18599 }, &xsubi);
141
142 try std.testing.expectEqual(1878644360, nrand48(&xsubi));
143 try std.testing.expectEqualSlices(c_ushort, &.{ 36678, 44304, 57331 }, &xsubi);
144
145 try std.testing.expectEqual(2114923686, nrand48(&xsubi));
146 try std.testing.expectEqualSlices(c_ushort, &.{ 58585, 22861, 64542 }, &xsubi);
147}
lib/c/stdlib/rand.zig created+30
...@@ -0,0 +1,30 @@
1const std = @import("std");
2const common = @import("../common.zig");
3const builtin = @import("builtin");
4
5comptime {
6 if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
7 @export(&rand, .{ .name = "rand", .linkage = common.linkage, .visibility = common.visibility });
8 @export(&srand, .{ .name = "srand", .linkage = common.linkage, .visibility = common.visibility });
9 @export(&rand_r, .{ .name = "rand_r", .linkage = common.linkage, .visibility = common.visibility });
10 }
11}
12
13// NOTE: The PRNG used for `rand` is unspecified, so it can be any!
14var rand_state: std.Random.SplitMix64 = .init(1);
15
16fn rand_r(seed: *c_uint) callconv(.c) c_int {
17 var mix: std.Random.SplitMix64 = .init(seed.*);
18 defer seed.* = @truncate(mix.s);
19
20 // Every bundled libc defines RAND_MAX as `std.math.maxInt(u31)` (except windows where it is `std.math.maxInt(u15)`)
21 return @as(u31, @truncate(mix.next() >> 33));
22}
23
24fn srand(seed: c_uint) callconv(.c) void {
25 rand_state = .init(seed);
26}
27
28fn rand() callconv(.c) c_int {
29 return @as(u31, @truncate(rand_state.next() >> 33));
30}
lib/libc/musl/src/prng/__rand48_step.c deleted-14
...@@ -1,14 +0,0 @@
1#include <stdint.h>
2#include "rand48.h"
3
4uint64_t __rand48_step(unsigned short *xi, unsigned short *lc)
5{
6 uint64_t a, x;
7 x = xi[0] | xi[1]+0U<<16 | xi[2]+0ULL<<32;
8 a = lc[0] | lc[1]+0U<<16 | lc[2]+0ULL<<32;
9 x = a*x + lc[3];
10 xi[0] = x;
11 xi[1] = x>>16;
12 xi[2] = x>>32;
13 return x & 0xffffffffffffull;
14}
lib/libc/musl/src/prng/__seed48.c deleted-3
...@@ -1,3 +0,0 @@
1#include "rand48.h"
2
3unsigned short __seed48[7] = { 0, 0, 0, 0xe66d, 0xdeec, 0x5, 0xb };
lib/libc/musl/src/prng/drand48.c deleted-17
...@@ -1,17 +0,0 @@
1#include <stdlib.h>
2#include <inttypes.h>
3#include "rand48.h"
4
5double erand48(unsigned short s[3])
6{
7 union {
8 uint64_t u;
9 double f;
10 } x = { 0x3ff0000000000000ULL | __rand48_step(s, __seed48+3)<<4 };
11 return x.f - 1.0;
12}
13
14double drand48(void)
15{
16 return erand48(__seed48);
17}
lib/libc/musl/src/prng/lcong48.c deleted-8
...@@ -1,8 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3#include "rand48.h"
4
5void lcong48(unsigned short p[7])
6{
7 memcpy(__seed48, p, sizeof __seed48);
8}
lib/libc/musl/src/prng/lrand48.c deleted-13
...@@ -1,13 +0,0 @@
1#include <stdlib.h>
2#include <inttypes.h>
3#include "rand48.h"
4
5long nrand48(unsigned short s[3])
6{
7 return __rand48_step(s, __seed48+3) >> 17;
8}
9
10long lrand48(void)
11{
12 return nrand48(__seed48);
13}
lib/libc/musl/src/prng/mrand48.c deleted-13
...@@ -1,13 +0,0 @@
1#include <stdlib.h>
2#include <inttypes.h>
3#include "rand48.h"
4
5long jrand48(unsigned short s[3])
6{
7 return (int32_t)(__rand48_step(s, __seed48+3) >> 16);
8}
9
10long mrand48(void)
11{
12 return jrand48(__seed48);
13}
lib/libc/musl/src/prng/rand.c deleted-15
...@@ -1,15 +0,0 @@
1#include <stdlib.h>
2#include <stdint.h>
3
4static uint64_t seed;
5
6void srand(unsigned s)
7{
8 seed = s-1;
9}
10
11int rand(void)
12{
13 seed = 6364136223846793005ULL*seed + 1;
14 return seed>>33;
15}
lib/libc/musl/src/prng/rand48.h deleted-5
...@@ -1,5 +0,0 @@
1#include <stdint.h>
2#include <features.h>
3
4hidden uint64_t __rand48_step(unsigned short *xi, unsigned short *lc);
5extern hidden unsigned short __seed48[7];
lib/libc/musl/src/prng/rand_r.c deleted-15
...@@ -1,15 +0,0 @@
1#include <stdlib.h>
2
3static unsigned temper(unsigned x)
4{
5 x ^= x>>11;
6 x ^= x<<7 & 0x9D2C5680;
7 x ^= x<<15 & 0xEFC60000;
8 x ^= x>>18;
9 return x;
10}
11
12int rand_r(unsigned *seed)
13{
14 return temper(*seed = *seed * 1103515245 + 12345)/2;
15}
lib/libc/musl/src/prng/seed48.c deleted-11
...@@ -1,11 +0,0 @@
1#include <stdlib.h>
2#include <string.h>
3#include "rand48.h"
4
5unsigned short *seed48(unsigned short *s)
6{
7 static unsigned short p[3];
8 memcpy(p, __seed48, sizeof p);
9 memcpy(__seed48, s, sizeof p);
10 return p;
11}
lib/libc/musl/src/prng/srand48.c deleted-6
...@@ -1,6 +0,0 @@
1#include <stdlib.h>
2
3void srand48(long seed)
4{
5 seed48((unsigned short [3]){ 0x330e, seed, seed>>16 });
6}
lib/std/Random.zig+1
...@@ -26,6 +26,7 @@ pub const Sfc64 = @import("Random/Sfc64.zig");...@@ -26,6 +26,7 @@ pub const Sfc64 = @import("Random/Sfc64.zig");
26pub const RomuTrio = @import("Random/RomuTrio.zig");26pub const RomuTrio = @import("Random/RomuTrio.zig");
27pub const SplitMix64 = @import("Random/SplitMix64.zig");27pub const SplitMix64 = @import("Random/SplitMix64.zig");
28pub const ziggurat = @import("Random/ziggurat.zig");28pub const ziggurat = @import("Random/ziggurat.zig");
29pub const lcg = @import("Random/lcg.zig");
2930
30/// Any comparison of this field may result in illegal behavior, since it may be set to31/// Any comparison of this field may result in illegal behavior, since it may be set to
31/// `undefined` in cases where the random implementation does not have any associated32/// `undefined` in cases where the random implementation does not have any associated
lib/std/Random/lcg.zig created+28
...@@ -0,0 +1,28 @@
1//! Linear congruential generator
2//!
3//! X(n+1) = (a * Xn + c) mod m
4//!
5//! PRNG
6
7const std = @import("std");
8
9/// Linear congruent generator where the modulo is `std.math.maxInt(T)`,
10/// wrapping over the integer.
11pub fn Wrapping(comptime T: type) type {
12 return struct {
13 xi: T,
14 a: T,
15 c: T,
16
17 pub fn init(xi: T, a: T, c: T) LcgSelf {
18 return .{ .xi = xi, .a = a, .c = c };
19 }
20
21 pub fn next(lcg: *LcgSelf) T {
22 lcg.xi = (lcg.a *% lcg.xi) +% lcg.c;
23 return lcg.xi;
24 }
25
26 const LcgSelf = @This();
27 };
28}
src/libs/musl.zig-10
...@@ -1305,17 +1305,7 @@ const src_files = [_][]const u8{...@@ -1305,17 +1305,7 @@ const src_files = [_][]const u8{
1305 "musl/src/passwd/putgrent.c",1305 "musl/src/passwd/putgrent.c",
1306 "musl/src/passwd/putpwent.c",1306 "musl/src/passwd/putpwent.c",
1307 "musl/src/passwd/putspent.c",1307 "musl/src/passwd/putspent.c",
1308 "musl/src/prng/drand48.c",
1309 "musl/src/prng/lcong48.c",
1310 "musl/src/prng/lrand48.c",
1311 "musl/src/prng/mrand48.c",
1312 "musl/src/prng/__rand48_step.c",
1313 "musl/src/prng/rand.c",
1314 "musl/src/prng/random.c",1308 "musl/src/prng/random.c",
1315 "musl/src/prng/rand_r.c",
1316 "musl/src/prng/__seed48.c",
1317 "musl/src/prng/seed48.c",
1318 "musl/src/prng/srand48.c",
1319 "musl/src/process/aarch64/vfork.s",1309 "musl/src/process/aarch64/vfork.s",
1320 "musl/src/process/arm/vfork.s",1310 "musl/src/process/arm/vfork.s",
1321 "musl/src/process/execl.c",1311 "musl/src/process/execl.c",
src/libs/wasi_libc.zig-10
...@@ -885,16 +885,6 @@ const libc_top_half_src_files = [_][]const u8{...@@ -885,16 +885,6 @@ const libc_top_half_src_files = [_][]const u8{
885 "musl/src/network/inet_pton.c",885 "musl/src/network/inet_pton.c",
886 "musl/src/network/ntohl.c",886 "musl/src/network/ntohl.c",
887 "musl/src/network/ntohs.c",887 "musl/src/network/ntohs.c",
888 "musl/src/prng/drand48.c",
889 "musl/src/prng/lcong48.c",
890 "musl/src/prng/lrand48.c",
891 "musl/src/prng/mrand48.c",
892 "musl/src/prng/__rand48_step.c",
893 "musl/src/prng/rand.c",
894 "musl/src/prng/rand_r.c",
895 "musl/src/prng/__seed48.c",
896 "musl/src/prng/seed48.c",
897 "musl/src/prng/srand48.c",
898 "musl/src/regex/fnmatch.c",888 "musl/src/regex/fnmatch.c",
899 "musl/src/regex/regerror.c",889 "musl/src/regex/regerror.c",
900 "musl/src/search/hsearch.c",890 "musl/src/search/hsearch.c",