Skip to content

Commit f2c3c5e

Browse files
committed
crrand: add Perm64
Add a type Perm64 that implements a psedurandom permutation of 64 bit integers.
1 parent 601f030 commit f2c3c5e

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

crrand/perm.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 Perm64 from a 64-bit seed, providing a
22+
// deterministic, pseudorandom, 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, pseudorandom permutation of 64-bit values.
43+
type Perm64 struct {
44+
seed [4]uint32
45+
}
46+
47+
// At 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 pseudorandom.
49+
func (p Perm64) At(n uint64) uint64 {
50+
// Use a simple Feistel network with 4 rounds to shuffle data.
51+
L := uint32(n >> 32)
52+
R := uint32(n)
53+
for r := range p.seed {
54+
t := arx(R^p.seed[r], p.seed[(r+1)&3])
55+
L, R = R, L^t
56+
}
57+
return (uint64(L) << 32) | uint64(R)
58+
}
59+
60+
// IndexOf inverts the permutation, returning the index of the provided value in
61+
// the permutation. If y was produced by At(x), then IndexOf(y) returns x.
62+
func (p Perm64) IndexOf(y uint64) uint64 {
63+
L := uint32(y >> 32)
64+
R := uint32(y)
65+
for r := 3; r >= 0; r-- {
66+
// reverse of: L, R = R, L ^ arx(R^k[r], k[(r+1)&3])
67+
prevR := L
68+
prevL := R ^ arx(prevR^p.seed[r], p.seed[(r+1)&3])
69+
L, R = prevL, prevR
70+
}
71+
return (uint64(L) << 32) | uint64(R)
72+
}
73+
74+
// ARX-only round function.
75+
func arx(x, k uint32) uint32 {
76+
x ^= k
77+
x += bits.RotateLeft32(x, 5)
78+
x ^= bits.RotateLeft32(x, 7)
79+
x += bits.RotateLeft32(x, 16)
80+
return x
81+
}

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+
p := MakePerm64(seed)
33+
for _, x := range interestingUint64s {
34+
y := p.At(x)
35+
x2 := p.IndexOf(y)
36+
if x != x2 {
37+
t.Errorf(".At(%d) = %d, .IndexOf(%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+
p := MakePerm64(rng.Uint64())
52+
for i := 0; i < 1000; i++ {
53+
x := rng.Uint64()
54+
y := p.At(x)
55+
x2 := p.IndexOf(y)
56+
if x != x2 {
57+
t.Errorf("p.At(%d) = %d, p.IndexOf(%d) = %d, want %d", x, y, y, x2, x)
58+
}
59+
}
60+
}

0 commit comments

Comments
 (0)