diff --git a/examples/gno.land/p/demo/pcg/gno.mod b/examples/gno.land/p/demo/pcg/gno.mod new file mode 100644 index 00000000000..805bdf036ab --- /dev/null +++ b/examples/gno.land/p/demo/pcg/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/pcg diff --git a/examples/gno.land/p/demo/pcg/pcg32.gno b/examples/gno.land/p/demo/pcg/pcg32.gno new file mode 100644 index 00000000000..7b9ba8149dd --- /dev/null +++ b/examples/gno.land/p/demo/pcg/pcg32.gno @@ -0,0 +1,242 @@ +package pcg + +import "encoding/binary" + +// ref: https://gist.github.com/ivan-pi/060e38d5f9a86c57923a61fbf18d095c +const ( + defaultState = 0x853c49e6748fea9b // 9600629759793949339 + multiplier = 0x5851f42d4c957f2d // 6364136223846793005 + incrementStep = 0x9e3779b97f4a7c15 // https://www.pcg-random.org/posts/bugs-in-splitmix.html +) + +// PCG32 is a 32-bit pseudorandom number generator based on the PCG family of algorithms. +type PCG32 struct { + state, increment uint64 +} + +// NewPCG32 creates a new PCG32 generator with the default state and sequence values. +func NewPCG32() *PCG32 { + return &PCG32{ + state: defaultState, + increment: incrementStep, + } +} + +// Seed initializes the PCG32 generator with the given state and sequence values. +func (p *PCG32) Seed(state, sequence uint64) *PCG32 { + p.increment = (sequence << 1) | 1 + p.state = (state+p.increment)*multiplier + incrementStep + return p +} + +// neg_mask is a constant used in the rotation operation to calculate the left rotation amount. +const neg_mask = 31 + +// Uint32 generates a pseudorandom 32-bit unsigned integer using the PCG32 algorithm. +// It updates the internal state of the generator using the PCG32 formula: +// +// state = state * multiplier + increment +// +// It then applies a series of bitwise operations to the old state to produce the random number: +// 1. XOR the old state with (old state >> 18). +// 2. Shift the result right by 27 bits to obtain `xorshifted`. +// 3. Calculate the rotation amount `rot` by shifting the old state right by 59 bits. +// 4. Rotate `xorshifted` right by `rot` bits and OR it with `xorshifted` rotated left by `((-rot) & 31)` bits. +// +// The resulting value is returned as the random number. +func (p *PCG32) Uint32() uint32 { + old := p.state + p.state = old*multiplier + p.increment + + xorshifted := uint32(((old >> 18) ^ old) >> 27) + rot := uint32(old >> 59) + + return (xorshifted >> rot) | (xorshifted << (neg_mask - rot)) +} + +// Uintn32 generates a pseudorandom number in the range [0, bound) using the PCG32 algorithm. +// If bound is 0, it returns 0. +// +// Parameters: +// - bound: The upper bound (exclusive) of the desired range. +// +// Return value: +// - A pseudorandom number in the range [0, bound). +// +// The function calculates a threshold value to ensure a uniform distribution of the generated numbers. +// It repeatedly generates random numbers using the Uint32 function until a number within the desired range is obtained. +func (p *PCG32) Uintn32(bound uint32) uint32 { + if bound == 0 { + return 0 + } + + threshold := -bound % bound + for { + r := p.Uint32() + if r >= threshold { + return r % bound + } + } +} + +// Uint63 generates a pseudorandom 63-bit integer using two 32-bit numbers. +// The function ensures that the returned number is within the range of 0 to 2^63-1. +func (p *PCG32) Uint63() int64 { + upper := int64(p.Uint32()) & 0x7FFFFFFF // Use only the lower 31 bits of the upper half + lower := int64(p.Uint32()) // Use all 32 bits of the lower half + return (upper << 32) | lower // Combine the two halves to form a 63-bit integer +} + +// advancedLCG64 is an implementation of a 64-bit linear congruential generator (LCG). +// It takes the following parameters: +// - state: The current state of the LCG. +// - delta: The number of steps to advance the LCG. +// - mul: The multiplier of the LCG. +// - add: The increment of the LCG. +// +// The function advances the LCG by `delta` steps and returns the new state. +// +// The LCG algorithm is defined by the following recurrence relation: +// +// state(n+1) = (state(n) * mul + add) mod 2^64 +// +// The function uses an efficient algorithm to advance the LCG by `delta` steps +// without iterating `delta` times. It exploits the properties of the LCG and +// uses binary exponentiation to calculate the result in logarithmic time. +// +// The algorithm works as follows: +// 1. Initialize `accMul` to 1 and `accAdd` to 0. +// 2. Iterate while `delta` is greater than 0: +// - If the least significant bit of `delta` is 1: +// - Multiply `accMul` by `mul`. +// - Set `accAdd` to `accAdd * mul + add`. +// - Update `add` to `(mul + 1) * add`. +// - Update `mul` to `mul * mul`. +// - Right-shift `delta` by 1 (divide by 2). +// 3. Return `accMul * state + accAdd`. +// +// The time complexity of this function is O(log(delta)), as it iterates logarithmically +// with respect to `delta`. The space complexity is O(1), as it uses only a constant +// amount of additional memory. +func (p *PCG32) advancedLCG64(state, delta, mul, add uint64) uint64 { + accMul := uint64(1) + accAdd := uint64(0) + + for delta > 0 { + if delta&1 != 0 { + accMul *= mul + accAdd = accAdd*mul + add + } + add = (mul + 1) * add + mul *= mul + delta /= 2 + } + return accMul*state + accAdd +} + +// Advance moves the PCG32 generator forward by `delta` steps. +// It updates the internal state of the generator using the `lcg64` function +// and returns the updated PCG32 instance. +func (p *PCG32) Advance(delta uint64) *PCG32 { + p.state = p.advancedLCG64(p.state, delta, multiplier, incrementStep) + return p +} + +// Retreat moves the PCG32 generator backward by `delta` steps. +// It calculates the equivalent forward delta using the two's complement of `delta` +// and calls the `Advance` function with the calculated delta. +// It returns the updated PCG32 instance. +func (p *PCG32) Retreat(delta uint64) *PCG32 { + safeDelta := ^delta + 1 + return p.Advance(safeDelta) +} + +// Shuffle shuffles the elements of a slice in-place using the Fisher-Yates shuffle algorithm. +// +// Parameters: +// - n: The number of elements to shuffle. +// - swap: A function that swaps the elements at indices i and j in the slice. +// +// Panics: +// - If n is negative. +// +// The function uses the Fisher-Yates shuffle algorithm to randomly permute the elements of the slice. +// It starts from the last element and iteratively swaps it with a randomly selected element from the remaining unshuffled portion of the slice. +// The random selection is performed using the Uintn32 function to generate a random index within the range [0, i+1). +func (p *PCG32) Shuffle(n int, swap func(i, j int)) { + if n < 0 { + panic("invalid argument to shuffle") + } + if n < 2 { + return + } + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + for i := n - 1; i > 0; i-- { + j := int(p.Uintn32(uint32(i + 1))) + swap(i, j) + } +} + +// Perm returns a slice of n integers representing a random permutation of the integers in the range [0, n). +// +// Parameters: +// - n: The number of integers to include in the permutation. +// +// Return value: +// - A slice of n integers representing a random permutation of the integers [0, n). +// +// The function initializes a slice with the integers [0, n) in ascending order. +// It then applies the Fisher-Yates shuffle algorithm to the slice, swapping elements at random positions. +// The resulting shuffled slice represents a random permutation of the integers [0, n). +func (p *PCG32) Perm(n int) []int { + res := make([]int, n) + for i := 0; i < n; i++ { + res[i] = i + } + for i := 1; i < n; i++ { + j := int(p.Uintn32(uint32(i + 1))) + res[i], res[j] = res[j], res[i] + } + return res +} + +// Read generates and fills the given byte slice with random bytes using the PCG32 random number generator. +// +// This function repeatedly generates 32-bit unsigned integers using the Uint32 function, +// and uses binary.LittleEndian.PutUint32 to split these integers into bytes and store them in the slice. +// This approach efficiently copies memory in accordance with the CPU's endian configuration, thereby enhancing performance. +// +// Parameters: +// - buf: The byte slice to be filled with random bytes. +// +// Return values: +// - n: The number of bytes generated and stored in the byte slice. It is always equal to len(buf). +// - err: Always returns nil, indicating no error occurred. +func (p *PCG32) Read(buf []byte) (int, error) { + n := len(buf) + i := 0 + + // loop unrolling: process 8 bytes in each iteration + for ; i <= n-8; i += 8 { + val1 := p.Uint32() + val2 := p.Uint32() + binary.LittleEndian.PutUint32(buf[i:], val1) + binary.LittleEndian.PutUint32(buf[i+4:], val2) + } + + // handle remaining bytes (less than 8 bytes) + if i < n { + remaining := buf[i:] + for j := 0; j < len(remaining); j += 4 { + if i+j < n { + val := p.Uint32() + // handle remaining bytes (less than 4 bytes) + for k := 0; k < 4 && (j+k) < len(remaining); k++ { + remaining[j+k] = byte(val >> (8 * k)) + } + } + } + } + + return n, nil +} diff --git a/examples/gno.land/p/demo/pcg/pcg32_test.gno b/examples/gno.land/p/demo/pcg/pcg32_test.gno new file mode 100644 index 00000000000..ce17acf5417 --- /dev/null +++ b/examples/gno.land/p/demo/pcg/pcg32_test.gno @@ -0,0 +1,329 @@ +package pcg + +import ( + "math" + "testing" +) + +func TestPCG32_Uintn32(t *testing.T) { + pcg := NewPCG32().Seed(12345, 67890) + + testCases := []struct { + bound uint32 + }{ + {0}, + {1}, + {10}, + {100}, + {1000}, + {10000}, + } + + for _, tc := range testCases { + result := pcg.Uintn32(tc.bound) + if tc.bound != 0 && result >= tc.bound { + t.Errorf("Bounded(%d) = %d; expected a value between 0 and %d", tc.bound, result, tc.bound) + } + if tc.bound == 0 && result != 0 { + t.Errorf("Bounded(%d) = %d; expected 0", tc.bound, result) + } + } +} + +func TestUint63PCG64(t *testing.T) { + pcg := NewPCG64(42, 54) + pcg.Seed(42, 54, 18, 27) + for i := 0; i < 100; i++ { + val := pcg.Uint63() + if val < 0 || val > math.MaxInt64 { + t.Errorf("Value out of bounds: %d", val) + } + } +} + +func TestPCG32_UniformDistribution(t *testing.T) { + pcg := NewPCG32().Seed(12345, 67890) + numBins := 10 + numSamples := 1000000 + toleranceRatio := 10 // 10% tolerance + bins := make([]int, numBins) + + for i := 0; i < numSamples; i++ { + r := pcg.Uint32() + binIndex := int(uint64(r) * uint64(numBins) >> 32) + bins[binIndex]++ + } + + expected := numSamples / numBins + tolerance := expected / toleranceRatio + + for _, count := range bins { + if abs(count-expected) > tolerance { + t.Errorf("bin count %d is outside the expected range [%d, %d]", count, expected-tolerance, expected+tolerance) + } + } +} + +func TestPCG32_lcg64(t *testing.T) { + pcg := NewPCG32() + + tests := []struct { + state uint64 + delta uint64 + expected uint64 + }{ + {1, 1, 17764851043169991490}, + {1, 10, 13321747199226635079}, + {1, 100, 7812368804252231469}, + {1, 1000, 7287210203119977849}, + {1, 10000, 13461019320290411953}, + } + + for _, tt := range tests { + result := pcg.advancedLCG64(tt.state, tt.delta, multiplier, incrementStep) + if result != tt.expected { + t.Errorf("lcg64(%d, %d) = %d; expected %d", tt.state, tt.delta, result, tt.expected) + } + } +} + +func TestPCG32_Advance(t *testing.T) { + pcg := NewPCG32() + + tests := []struct { + state uint64 + delta uint64 + expected uint64 + }{ + {1, 1, 17764851043169991490}, + {1, 10, 13321747199226635079}, + {1, 100, 7812368804252231469}, + {1, 1000, 7287210203119977849}, + {1, 10000, 13461019320290411953}, + } + + for _, tt := range tests { + pcg.state = tt.state + pcg.Advance(tt.delta) + if pcg.state != tt.expected { + t.Errorf("Advance(%d) = %d; expected %d", tt.delta, pcg.state, tt.expected) + } + } +} + +func TestPCG32_Retreat(t *testing.T) { + pcg := NewPCG32() + + tests := []struct { + initialState uint64 + delta uint64 + expectedState uint64 + }{ + {3643462645497912072, 1, 2297219049549015711}, + {15256603694110904427, 10, 9089490691196273925}, + {5234694153321213237, 100, 9614261562837832073}, + {2323235076269450313, 1000, 1018981208295873745}, + {6143568259046921169, 10000, 9666238883299984929}, + } + + for _, tt := range tests { + pcg.state = tt.initialState + pcg.Retreat(tt.delta) + if pcg.state != tt.expectedState { + t.Errorf("Retreat(%d) = %d; expected %d", tt.delta, pcg.state, tt.expectedState) + } + } +} + +func abs(x int) int { + if x < 0 { + return -x + } + return x +} + +func TestPCG32_Shuffle(t *testing.T) { + pcg := NewPCG32().Seed(12345, 67890) + + testCases := []struct { + n int + expected []int + }{ + {0, []int{}}, + {1, []int{0}}, + {2, []int{0, 1}}, + {3, []int{0, 1, 2}}, + {4, []int{0, 1, 2, 3}}, + {5, []int{0, 1, 2, 3, 4}}, + } + + for _, tc := range testCases { + arr := make([]int, tc.n) + for i := range arr { + arr[i] = i + } + + pcg.Shuffle(tc.n, func(i, j int) { + arr[i], arr[j] = arr[j], arr[i] + }) + + if tc.n > 1 && isArrayEqual(arr, tc.expected) { + t.Errorf("Shuffle(%d) = %v; expected a shuffled version of %v", tc.n, arr, tc.expected) + } + } +} + +func isArrayEqual(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestPCG32_Perm(t *testing.T) { + pcg := NewPCG32().Seed(12345, 67890) + + tests := []struct { + n int + expected []int + }{ + {0, []int{}}, + {1, []int{0}}, + {2, []int{0, 1}}, + {3, []int{0, 1, 2}}, + {4, []int{0, 1, 2, 3}}, + {5, []int{0, 1, 2, 3, 4}}, + } + + for _, tt := range tests { + result := pcg.Perm(tt.n) + if !isPermutation(result, tt.expected) { + t.Errorf("Perm(%d) = %v; expected a permutation of %v", tt.n, result, tt.expected) + } + } +} + +func isPermutation(a, b []int) bool { + if len(a) != len(b) { + return false + } + counts := make(map[int]int) + for _, num := range a { + counts[num]++ + } + for _, num := range b { + if counts[num] == 0 { + return false + } + counts[num]-- + } + return true +} + +func TestPCG32_Read(t *testing.T) { + tests := []struct { + name string + seed uint64 + bufSize int + expected []byte + }{ + { + name: "Read 0 bytes", + seed: 9876543210, + bufSize: 0, + expected: []byte{}, + }, + { + name: "Read 1 byte", + seed: 42, + bufSize: 1, + expected: []byte{0xb0}, + }, + { + name: "Read 7 bytes", + seed: 42, + bufSize: 7, + expected: []byte{0xb0, 0xfd, 0xe1, 0x6a, 0xe8, 0x9, 0xe4}, + }, + { + name: "Read 16 bytes", + seed: 42, + bufSize: 16, + expected: []byte{ + 0xb0, 0xfd, 0xe1, 0x6a, + 0xe8, 0x9, 0xe4, 0x3d, + 0x50, 0x24, 0x2c, 0x70, + 0xf0, 0xae, 0x88, 0x77, + }, + }, + { + name: "Read 32 bytes", + seed: 1234567890, + bufSize: 32, + expected: []byte{ + 0x64, 0x44, 0xd9, 0x30, + 0x8, 0x8, 0x49, 0x6e, + 0x3f, 0xbd, 0xfe, 0xbf, + 0x30, 0x76, 0xe8, 0x56, + 0x45, 0xcd, 0xa1, 0xba, + 0xd4, 0xda, 0x3e, 0x62, + 0xa7, 0xa9, 0xf0, 0x9c, + 0x1b, 0x75, 0x2, 0xad, + }, + }, + { + name: "Read 64 bytes", + seed: 9876543210, + bufSize: 64, + expected: []byte{ + 0xf5, 0xde, 0x18, 0xa3, + 0x19, 0x9b, 0xbb, 0xd6, + 0xc7, 0x7c, 0x69, 0xb3, + 0xaa, 0x21, 0x15, 0x69, + 0xbc, 0xea, 0x78, 0x1d, + 0xce, 0x78, 0xf8, 0x46, + 0xae, 0x98, 0xe4, 0x20, + 0xe2, 0x16, 0x6, 0x6d, + 0x3e, 0x99, 0x96, 0x66, + 0xd, 0xfc, 0x14, 0x79, + 0x4e, 0x59, 0x42, 0x25, + 0x9, 0x39, 0x41, 0x95, + 0x11, 0x3c, 0xa1, 0xa9, + 0xd2, 0x2, 0x10, 0x51, + 0x6e, 0xaa, 0xe4, 0x25, + 0xa8, 0x45, 0xe0, 0x76, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + p := NewPCG32().Seed(tc.seed, 0) + + buf := make([]byte, tc.bufSize) + + n, err := p.Read(buf) + if err != nil { + t.Fatalf("Read() error = %v; want nil", err) + } + + // assert.Equal(t, tc.bufSize, n) + // assert.Equal(t, tc.expected, buf) + if tc.bufSize != n { + t.Errorf("Read() = %d; expected %d", n, tc.bufSize) + } + + for i := 0; i < tc.bufSize; i++ { + if buf[i] != tc.expected[i] { + t.Errorf("Read() = %v; expected %v", buf, tc.expected) + break + } + } + }) + } +} diff --git a/examples/gno.land/p/demo/pcg/pcg64.gno b/examples/gno.land/p/demo/pcg/pcg64.gno new file mode 100644 index 00000000000..8b1c5a26d3d --- /dev/null +++ b/examples/gno.land/p/demo/pcg/pcg64.gno @@ -0,0 +1,217 @@ +package pcg + +import ( + "encoding/binary" + "errors" + "math" + "math/bits" +) + +const ( + inv52 = 1.0 / (1 << 52) + // inv64 = 1.0 / float64(math.MaxUint64) +) + +// A PCG64 is a PCG64 generator with 128 bits of internal state. +// A zero PCG64 is equivalent to one seeded with 0. +type PCG64 struct { + hi, lo *PCG32 +} + +// NewPCG64 returns a new PCG64 generator seeded with thr given values. +// seed1 and seed2 are the initial state values for the generator. +func NewPCG64(seed1, seed2 uint64) *PCG64 { + return &PCG64{ + hi: NewPCG32().Seed(seed1, 0), + lo: NewPCG32().Seed(seed2, 0), + } +} + +// Seed initializes the PCG64 generator with the given state and sequence values. +// seed1 and seed2 are the initial state values, and seq1 and seq2 are the sequence values. +func (p *PCG64) Seed(seed1, seed2, seq1, seq2 uint64) *PCG64 { + mask := ^uint64(0) >> 1 + if seq1&mask == seq2&mask { + seq2 = ^seq2 + } + p.lo.Seed(seed1, seq1) + p.hi.Seed(seed2, seq2) + + return p +} + +// Uint64 generates a pseudorandom 64-bit unsigned integer using the PCG64 algorithm. +func (p *PCG64) Uint64() uint64 { + return uint64(p.hi.Uint32())<<32 | uint64(p.lo.Uint32()) +} + +// Uint63 generates a pseudorandom 63-bit integer using the PCG64 algorithm. +// It masks the highest bit to ensure the value is within the 63-bit integer range. +func (p *PCG64) Uint63() int64 { + return int64(p.Uint64() & 0x7FFFFFFFFFFFFFFF) // Mask the highest bit to stay within the 63-bit range +} + +// Uint64n generates a pseudorandom number in the range [0, bound) using the PCG64 algorithm. +func (p *PCG64) Uint64n(bound uint64) uint64 { + threshold := -bound % bound + for { + r := p.Uint64() + if r >= threshold { + return r % bound + } + } +} + +// Float64 returns a random float64 in the range [0.0, 1.0). +func (p *PCG64) Float64() float64 { + return float64(p.Uint63()>>11) * inv52 +} + +// Float64Full uses the full 64 bits of the generated number to produce a random float64. +// slightly more precise than Float64() but slower. +func (p *PCG64) Float64Full() float64 { + return float64(p.Uint64()&0xFFFFFFFFFFFFFF) / math.MaxUint64 +} + +// Advance moves the PCG64 generator forward by `delta` steps. +// It updates the initial state of the generator. +func (p *PCG64) Advance(delta uint64) *PCG64 { + p.hi.Advance(delta) + p.lo.Advance(delta) + return p +} + +// Retreat moves the PCG64 generator backward by `delta` steps. +// it updates the initial state of the generator. +func (p *PCG64) Retreat(delta uint64) *PCG64 { + safeDelta := ^uint64(0) - 1 + p.Advance(safeDelta) + return p +} + +func (p *PCG64) Shuffle(n int, swap func(i, j int)) { + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + for i := n - 1; i > 0; i-- { + j := int(p.Uint64n(uint64(i + 1))) + swap(i, j) + } +} + +func (p *PCG64) Perm(n int) []int { + res := make([]int, n) + for i := range res { + res[i] = i + } + p.Shuffle(n, func(i, j int) { + res[i], res[j] = res[j], res[i] + }) + return res +} + +// Read generates random bytes in the provided byte slice using the PCG64 random number generator. +// It employs loop unrolling to process 16 bytes at a time for performance enhancement. +func (p *PCG64) Read(buf []byte) (int, error) { + n := len(buf) + i := 0 + + // Loop unrolling: processing 16 bytes per iteration + for ; i <= n-16; i += 16 { + val1 := p.Uint64() + val2 := p.Uint64() + binary.LittleEndian.PutUint64(buf[i:], val1) + binary.LittleEndian.PutUint64(buf[i+8:], val2) + } + + // Handle any remaining bytes that were not processed in the main loop + if i < n { + remaining := buf[i:] + for j := 0; j < len(remaining); j += 8 { + if i+j < n { + val := p.Uint64() + // Only write the necessary bytes + for k := 0; k < 8 && (j+k) < len(remaining); k++ { + remaining[j+k] = byte(val >> (8 * k)) + } + } + } + } + + return n, nil +} + +func beUint64(b []byte) uint64 { + _ = b[7] + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 +} + +func bePutUint64(b []byte, v uint64) { + _ = b[7] + b[0] = byte(v >> 56) + b[1] = byte(v >> 48) + b[2] = byte(v >> 40) + b[3] = byte(v >> 32) + b[4] = byte(v >> 24) + b[5] = byte(v >> 16) + b[6] = byte(v >> 8) + b[7] = byte(v) +} + +// MarshalBinaryPCG64 serializes the state of the PCG64 generator to a binary format. +// It returns the serialized state as a byte slice. +func (p *PCG64) MarshalBinaryPCG64() ([]byte, error) { + b := make([]byte, 20) + copy(b, "pcg:") + bePutUint64(b[4:], p.hi.state) + bePutUint64(b[4+8:], p.lo.state) + return b, nil +} + +var errUnmarshalPCG = errors.New("invalid PCG encoding") + +// UnmarshalBinaryPCG64 deserializes the state of the PCG64 generator from a binary format. +// It takes the serialized state as a byte slice and updates the generator's state. +func (p *PCG64) UnmarshalBinary(b []byte) error { + if len(b) != 20 || string(b[:4]) != "pcg:" { + return errUnmarshalPCG + } + p.hi.state = beUint64(b[4:]) + p.lo.state = beUint64(b[4+8:]) + return nil +} + +func (p *PCG64) next() (uint64, uint64) { + const ( + mulHi = 2549297995355413924 + mulLo = 4865540595714422341 + incHi = 6364136223846793005 + incLo = 1442695040888963407 + ) + + // state = state * mul + inc + hi, lo := bits.Mul64(p.lo.state, mulLo) + hi += p.hi.state*mulLo + p.lo.state*mulHi + + lo, c := bits.Add64(lo, incLo, 0) + hi, _ = bits.Add64(hi, incHi, c) + + p.lo.state = lo + p.hi.state = hi + + return hi, lo +} + +// NextUInt64WithMCG generates a pseudorandom 64-bit unsigned integer using the PCG64 algorithm with Multiplier Congruential Generator (MCG). +// It updates the internal state of the generator and returns the generated value. +func (p *PCG64) Uint64nWithMCG() uint64 { + hi, lo := p.next() + + // ref: https://www.pcg-random.org/posts/128-bit-mcg-passes-practrand.html (#64-bit Multiplier) + const cheapMul = 0xda942042e4dd58b5 // 15750249268501108917 + hi ^= hi >> 22 + hi *= cheapMul + hi ^= hi >> 48 + hi *= (lo | 1) + + return hi +} diff --git a/examples/gno.land/p/demo/pcg/pcg64_test.gno b/examples/gno.land/p/demo/pcg/pcg64_test.gno new file mode 100644 index 00000000000..1b18985201c --- /dev/null +++ b/examples/gno.land/p/demo/pcg/pcg64_test.gno @@ -0,0 +1,198 @@ +package pcg + +import ( + "math" + "testing" + "time" +) + +func TestPCG_Uint63(t *testing.T) { + pcg := NewPCG64(12345, 67890) + + for i := 0; i < 100000; i++ { + val := pcg.Uint63() + if val < 0 { + t.Errorf("Uint63() = %d; want a non-negative number", val) + } + if val > 0x7FFFFFFFFFFFFFFF { + t.Errorf("Uint63() = %d; want a 63-bit integer", val) + } + } +} + +func TestPCG_Advance(t *testing.T) { + pcg := NewPCG64(12345, 67890) + + tests := []struct { + delta uint64 + expectedStateHi uint64 + expectedStateLo uint64 + }{ + {1, 5288318170876267920, 8586121761925336369}, + {10, 658778831176772942, 680070833917286903}, + {100, 11328633206433140426, 2423557365805501827}, + {1000, 14055077221971198434, 7063816904067589179}, + {10000, 3283529072340023762, 12459493142276168811}, + } + + for _, tc := range tests { + pcg.Advance(tc.delta) + if pcg.hi.state != tc.expectedStateHi { + t.Errorf("Advance(%d) hi state = %d; expected %d", tc.delta, pcg.hi.state, tc.expectedStateHi) + } + if pcg.lo.state != tc.expectedStateLo { + t.Errorf("Advance(%d) lo state = %d; expected %d", tc.delta, pcg.lo.state, tc.expectedStateLo) + } + } +} + +func TestPCG(t *testing.T) { + p := NewPCG64(1, 2) + want := []uint64{ + 0x6a3b45f887fad67c, + 0x1b0a91e2d7d75723, + 0x905a1518e26e8445, + 0x8cb6b7c0ea9f200c, + 0x59afa674b44b2509, + 0x86ab5e04d104bd4c, + 0x13e180669e2d07ea, + 0x5a6a0bb349dd26ae, + 0x1cd5f134a8581b57, + 0xc807c2686fe5baff, + 0x846b5fc66eb343cb, + 0x169adcefdd042f97, + 0x9b58ba4c9ef301fa, + 0xd54fc77c467d80bf, + 0xb776b6e2508ebab3, + 0xfd002241e862137b, + 0xc6a0fa2cfa1f95e9, + 0x67469082d8377e0a, + 0x7f30faccba8029d4, + 0xf200f5696d060925, + } + + for i, x := range want { + u := p.Uint64nWithMCG() + if u != x { + t.Errorf("PCG #%d = %#x, want %#x", i, u, x) + } + } +} + +func TestPCG_Retreat(t *testing.T) { + pcg := NewPCG64(12345, 67890) + + tests := []struct { + delta uint64 + expectedStateHi uint64 + expectedStateLo uint64 + }{ + {1, 9562276038148940761, 762819840426222678}, + {10, 6073181909808593307, 4206663794773473040}, + {100, 13320423970107457037, 15716292132216561082}, + {1000, 2035794469121007023, 329538305516032724}, + {10000, 8361027235883110657, 2874612980081388766}, + } + + for _, tt := range tests { + pcg.Retreat(tt.delta) + if pcg.hi.state != tt.expectedStateHi { + t.Errorf("Retreat(%d) hi state = %d; expected %d", tt.delta, pcg.hi.state, tt.expectedStateHi) + } + if pcg.lo.state != tt.expectedStateLo { + t.Errorf("Retreat(%d) lo state = %d; expected %d", tt.delta, pcg.lo.state, tt.expectedStateLo) + } + } +} + +func Test_ExamplePCG64_Shuffle(t *testing.T) { + pcg := NewPCG64(42, 54) + pcg.Seed(42, 54, 18, 27) + + array := []int{1, 2, 3, 4, 5} + pcg.Shuffle(len(array), func(i, j int) { + array[i], array[j] = array[j], array[i] + }) + + if len(array) != 5 { + t.Errorf("Shuffle() len(array) = %d; want 5", len(array)) + } + + if isArrayEqual(array, []int{1, 2, 3, 4, 5}) { + t.Errorf("Shuffle() array = %v; want shuffled", array) + } +} + +func TestFloat64(t *testing.T) { + pcg := NewPCG64(42, 54) + for i := 0; i < 1000; i++ { + val := pcg.Float64() + if val < 0.0 || val > 1.0 { + t.Errorf("Float64() returned a value out of bounds: %f", val) + } + } +} + +func TestFloat64Full(t *testing.T) { + pcg := NewPCG64(42, 54) + for i := 0; i < 1000; i++ { + val := pcg.Float64Full() + if val < 0.0 || val >= 1.0 { + t.Errorf("Float64Full() returned a value out of bounds: %f", val) + } + } +} + +func TestPCG64Read(t *testing.T) { + now := uint64(time.Now().UnixNano()) + testSizes := []int{16, 32, 48, 64, 100, 1023, 2048} + for _, size := range testSizes { + buf := make([]byte, size) + pcg := NewPCG64(12345, now) + n, err := pcg.Read(buf) + if err != nil { + t.Errorf("Read returned an error: %v", err) + } + if n != size { + t.Errorf("Read returned wrong number of bytes: got %v, want %v", n, size) + } + // Check if bytes are not all zero; this is a simplistic randomness check + allZero := true + for _, b := range buf { + if b != 0 { + allZero = false + break + } + } + if allZero { + t.Errorf("Buffer of size %d is filled with zeros, which is highly improbable", size) + } + } +} + +func TestPCG64ReadEdgeCases(t *testing.T) { + now := uint64(time.Now().UnixNano()) + edgeSizes := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 15, 17} + for _, size := range edgeSizes { + buf := make([]byte, size) + pcg := NewPCG64(12345, now) + n, err := pcg.Read(buf) + if err != nil { + t.Errorf("Read returned an error: %v", err) + } + if n != size { + t.Errorf("Read returned wrong number of bytes: got %v, want %v", n, size) + } + + nonZeroFound := false + for _, b := range buf { + if b != 0 { + nonZeroFound = true + break + } + } + if !nonZeroFound && size != 0 { + t.Errorf("Buffer of size %d has no non-zero bytes, which is highly improbable", size) + } + } +}