Skip to content

Commit d93509c

Browse files
committed
crrand: add Perm64
Add a type Perm64 that implements a psedurandom permutation of 64 bit integers.
1 parent 894974b commit d93509c

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed

crrand/perm.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
// Package crrand implements functionality related to pseudorandom number
16+
// generation.
17+
package crrand
18+
19+
import "math/bits"
20+
21+
// MakePerm64 constructs a new Mixer from a 64-bit seed, providing a
22+
// deterministic, pseduorandom, bijective mapping of 64-bit values X to 64-bit
23+
// values Y.
24+
func MakePerm64(seed uint64) Perm64 {
25+
// derive 4 x 32-bit round keys from the 64-bit seed using only ARX ops.
26+
const c0 = 0x9E3779B97F4A7C15 // golden ratio (used here as XOR salt)
27+
const c1 = 0xC2B2AE3D27D4EB4F // a constant
28+
29+
var m Perm64
30+
s0 := seed
31+
s1 := bits.RotateLeft64(seed^c0, 13)
32+
s2 := bits.RotateLeft64(seed^c1, 37)
33+
s3 := bits.RotateLeft64(seed^(c0^c1), 53)
34+
35+
m.seed[0] = uint32(s0)
36+
m.seed[1] = uint32(s1 >> 32)
37+
m.seed[2] = uint32(s2)
38+
m.seed[3] = uint32(s3 >> 32)
39+
return m
40+
}
41+
42+
// A Perm64 provides a deterministic, pseduorandom permutation of 64-bit values.
43+
type Perm64 struct {
44+
seed [4]uint32
45+
}
46+
47+
// Nth returns the nth value in the permutation of the 64-bit values. The return
48+
// value may be passed to Index to recover n. The permutation is pseduorandom.
49+
func (p Perm64) Nth(n uint64) uint64 {
50+
// Use a simple Feistel network with 4 rounds to shuffle data.
51+
52+
L := uint32(n >> 32)
53+
R := uint32(n)
54+
for r := range p.seed {
55+
t := f(R^p.seed[r], p.seed[(r+1)&3])
56+
L, R = R, L^t
57+
}
58+
return (uint64(L) << 32) | uint64(R)
59+
}
60+
61+
// Index inverts the permutation, returning the index of the provided value in
62+
// the permutation. If y was produced by Nth(x), then Index(y) returns x.
63+
func (p Perm64) Index(y uint64) uint64 {
64+
L := uint32(y >> 32)
65+
R := uint32(y)
66+
for r := 3; r >= 0; r-- {
67+
// reverse of: L, R = R, L ^ F(R^k[r], k[(r+1)&3])
68+
prevR := L
69+
prevL := R ^ f(prevR^p.seed[r], p.seed[(r+1)&3])
70+
L, R = prevL, prevR
71+
}
72+
return (uint64(L) << 32) | uint64(R)
73+
}
74+
75+
// ARX-only round function.
76+
func f(x, k uint32) uint32 {
77+
x ^= k
78+
x += bits.RotateLeft32(x, 5)
79+
x ^= bits.RotateLeft32(x, 7)
80+
x += bits.RotateLeft32(x, 16)
81+
return x
82+
}

crrand/perm_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
// implied. See the License for the specific language governing
13+
// permissions and limitations under the License.
14+
15+
package crrand
16+
17+
import (
18+
"math"
19+
"math/rand/v2"
20+
"testing"
21+
"time"
22+
)
23+
24+
var interestingUint64s = []uint64{
25+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 33, 63, 64, 65, 129, 3050, 29356,
26+
297532935, 2973539791203, 0x9E3779B97F4A7C15, math.MaxUint64 - 1,
27+
math.MaxUint64,
28+
}
29+
30+
func TestPerm64(t *testing.T) {
31+
for _, seed := range interestingUint64s {
32+
mixer := MakePerm64(seed)
33+
for _, x := range interestingUint64s {
34+
y := mixer.Nth(x)
35+
x2 := mixer.Index(y)
36+
if x != x2 {
37+
t.Errorf("seed.Mix(%d) = %d, seed.Unmix(%d) = %d, want %d", x, y, y, x2, x)
38+
}
39+
}
40+
}
41+
}
42+
43+
func TestPerm64Random(t *testing.T) {
44+
seed := uint64(time.Now().UnixNano())
45+
defer func() {
46+
if t.Failed() {
47+
t.Logf("seed: %d", seed)
48+
}
49+
}()
50+
rng := rand.New(rand.NewPCG(seed, seed))
51+
mixer := MakePerm64(rng.Uint64())
52+
for i := 0; i < 1000; i++ {
53+
x := rng.Uint64()
54+
y := mixer.Nth(x)
55+
x2 := mixer.Index(y)
56+
if x != x2 {
57+
t.Errorf("seed.Mix(%d) = %d, seed.Unmix(%d) = %d, want %d", x, y, y, x2, x)
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)