From ddb4d103201491c41a4baf450fb16f85fbde5095 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:53:25 +0200 Subject: [PATCH 1/6] Revert "feat(stdlibs): add math/rand (#2455)" This reverts commit f547d7dcac11de7937664413497fed35eb1a03ff. --- .github/workflows/docs-linter.yml | 4 +- .github/workflows/examples.yml | 5 +- CONTRIBUTING.md | 2 +- contribs/gnodev/go.mod | 2 +- contribs/gnofaucet/go.mod | 2 +- contribs/gnokeykc/go.mod | 2 +- contribs/gnomd/go.mod | 2 +- .../local-setup/installation.md | 16 +- .../validators/connect-to-existing-chain.md | 4 +- .../validators/setting-up-a-new-chain.md | 4 +- docs/reference/go-gno-compatibility.md | 5 +- examples/gno.land/p/demo/rand/gno.mod | 3 + examples/gno.land/p/demo/rand/rand.gno | 139 +++++++ .../gno.land/p/demo/rand/rand0_filetest.gno | 56 +++ examples/gno.land/p/demo/rand/rand_test.gno | 49 +++ examples/gno.land/r/demo/art/gnoface/gno.mod | 7 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 14 +- .../r/demo/art/gnoface/gnoface_test.gno | 70 ++-- examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 +- .../gno.land/r/x/manfred_outfmt/outfmt.gno | 15 +- .../r/x/manfred_outfmt/outfmt_test.gno | 22 +- gnovm/stdlibs/bytes/buffer_test.gno | 4 +- gnovm/stdlibs/bytes/bytes_test.gno | 6 +- gnovm/stdlibs/math/rand/auto_test.gno | 39 -- gnovm/stdlibs/math/rand/pcg.gno | 121 ------- gnovm/stdlibs/math/rand/pcg_test.gno | 78 ---- gnovm/stdlibs/math/rand/rand.gno | 340 ------------------ gnovm/stdlibs/math/rand/zipf.gno | 77 ---- gnovm/stdlibs/sort/sort_test.gno | 12 +- gnovm/tests/files/extern/p1/s2.gno | 2 + gnovm/tests/files/import4.gno | 2 +- gnovm/tests/imports.go | 13 +- go.mod | 2 +- misc/autocounterd/go.mod | 2 +- misc/devdeps/go.mod | 2 +- misc/docs-linter/go.mod | 2 +- misc/loop/go.mod | 2 +- tm2/pkg/libtm/README.md | 2 +- tm2/pkg/libtm/go.mod | 2 +- 39 files changed, 372 insertions(+), 764 deletions(-) create mode 100644 examples/gno.land/p/demo/rand/gno.mod create mode 100644 examples/gno.land/p/demo/rand/rand.gno create mode 100644 examples/gno.land/p/demo/rand/rand0_filetest.gno create mode 100644 examples/gno.land/p/demo/rand/rand_test.gno delete mode 100644 gnovm/stdlibs/math/rand/auto_test.gno delete mode 100644 gnovm/stdlibs/math/rand/pcg.gno delete mode 100644 gnovm/stdlibs/math/rand/pcg_test.gno delete mode 100644 gnovm/stdlibs/math/rand/rand.gno delete mode 100644 gnovm/stdlibs/math/rand/zipf.gno diff --git a/.github/workflows/docs-linter.yml b/.github/workflows/docs-linter.yml index e56c1a663ea..0ffa67dfe95 100644 --- a/.github/workflows/docs-linter.yml +++ b/.github/workflows/docs-linter.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.22' + go-version: '1.21' - name: Install dependencies run: go mod download @@ -28,4 +28,4 @@ jobs: run: make -C docs/ build - name: Run linter - run: make -C docs/ lint + run: make -C docs/ lint \ No newline at end of file diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index d11710344b1..cea52e74cd4 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,6 +20,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" runs-on: ubuntu-latest timeout-minutes: 30 @@ -35,6 +36,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -58,6 +60,7 @@ jobs: fail-fast: false matrix: goversion: + - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -105,4 +108,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc125a6da73..e041ab18875 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.22+) +- Go (version 1.21+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index 6d2c7a34293..ec82f09e467 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index e4b63c7a9e9..dc5267fff2b 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index 711cafed241..d9e785f5226 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 8bc352d4848..39043bae144 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnomd -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index a3658fa6ab3..8700ff9a2b2 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -5,15 +5,15 @@ id: installation # Installation ## Overview -In this tutorial, you will learn how to set up the Gno development environment -locally, so you can get up and running writing Gno code. You will download and +In this tutorial, you will learn how to set up the Gno development environment +locally, so you can get up and running writing Gno code. You will download and install all the necessary tooling, and validate that it is correctly configured to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: @@ -30,7 +30,7 @@ GitHub repository somewhere on disk: git clone https://github.com/gnolang/gno.git ``` -## 2. Installing the required tools +## 2. Installing the required tools There are three tools that should be used for getting started with Gno development: - `gno` - the GnoVM binary @@ -42,7 +42,7 @@ To install all three tools, simply run the following in the root of the repo: make install ``` -## 3. Verifying installation +## 3. Verifying installation ### `gno` `gno` provides ample functionality to the user, among which is running, @@ -59,7 +59,7 @@ You should get the help output from the command: ![gno help](../../assets/getting-started/local-setup/local-setup/gno-help.gif) -Alternatively, if you don't want to have the binary callable system-wide, you +Alternatively, if you don't want to have the binary callable system-wide, you can run the binary directly: ```bash @@ -68,8 +68,8 @@ go run ./cmd/gno --help ``` ### `gnodev` -`gnodev` is the go-to Gno development helper tool - it comes with a built in -Gno.land node, a `gnoweb` server to display the state of your smart contracts +`gnodev` is the go-to Gno development helper tool - it comes with a built in +Gno.land node, a `gnoweb` server to display the state of your smart contracts (realms), and a watcher system to actively track changes in your code. Read more about `gnodev` [here](../../gno-tooling/cli/gnodev.md). diff --git a/docs/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md index f1acf06049f..a1a337a5a48 100644 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -12,7 +12,7 @@ In this tutorial, you will learn how to start a local Gno node and connect to an - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -107,4 +107,4 @@ gnoland start \ That's it! 🎉 -Your new Gno node should be up and running, and syncing block data from the remote chain. +Your new Gno node should be up and running, and syncing block data from the remote chain. \ No newline at end of file diff --git a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md index 8f94037dc1d..1927679db8f 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -13,7 +13,7 @@ Additionally, you will see the different options you can use to make your Gno in - **Git** - **`make` (for running Makefiles)** -- **Go 1.22+** +- **Go 1.21+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -451,4 +451,4 @@ Genesis block generation happens only once during the lifetime of a Gno chain. This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from block 0), the specified balance sheet will not be applied. -::: +::: \ No newline at end of file diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index a2f83f2bbc6..89ad4f7b990 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -34,7 +34,7 @@ id: go-gno-compatibility Generics are currently not implemented. -Note that Gno does not support shadowing of built-in types. +Note that Gno does not support shadowing of built-in types. While the following built-in typecasting assignment would work in Go, this is not supported in Gno. ```go @@ -205,7 +205,7 @@ Legend: | math/big | `tbd` | | math/bits | `full` | | math/cmplx | `tbd` | -| math/rand | `full`[^9] | +| math/rand | `todo` | | mime | `tbd` | | mime/multipart | `tbd` | | mime/quotedprintable | `tbd` | @@ -291,7 +291,6 @@ Legend: determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. [^8]: `crypto/ed25519` is currently only implemented for `Verify`, which should still cover a majority of use cases. A full implementation is welcome. -[^9]: `math/rand` in Gno ports over Go's `math/rand/v2`. ## Tooling (`gno` binary) diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod new file mode 100644 index 00000000000..098af152648 --- /dev/null +++ b/examples/gno.land/p/demo/rand/gno.mod @@ -0,0 +1,3 @@ +// Draft + +module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/rand/rand.gno b/examples/gno.land/p/demo/rand/rand.gno new file mode 100644 index 00000000000..2fa16d627be --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand.gno @@ -0,0 +1,139 @@ +package rand + +// Disclaimer: this package is unsafe and won't prevent others to +// guess values in advance. +// +// the goal of this package is to implement a random library that +// is fully deterministic for validators while being hard to guess. +// +// We use the Bernstein's hash djb2 to be CPU-cycle efficient. + +import ( + "math/rand" + "std" + "time" +) + +type Instance struct { + seed int64 +} + +func New() *Instance { + r := Instance{seed: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed int64) *Instance { + r := Instance{seed: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() int64 { + return i.seed +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Int64(int64(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Int64(input int64) { + i.seed = (i.seed << 5) + i.seed + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + i.djb2Int64(height) + } + + // time + { + secs := time.Now().Second() + i.djb2Int64(int64(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Int64(int64(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Float32() float32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Float32() +} + +func (i *Instance) Float64() float64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Float64() +} + +func (i *Instance) Int() int { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int() +} + +func (i *Instance) Intn(n int) int { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Intn(n) +} + +func (i *Instance) Int63() int64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int63() +} + +func (i *Instance) Int63n(n int64) int64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int63n(n) +} + +func (i *Instance) Int31() int32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int31() +} + +func (i *Instance) Int31n(n int32) int32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Int31n(n) +} + +func (i *Instance) Uint32() uint32 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Uint32() +} + +func (i *Instance) Uint64() uint64 { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Uint64() +} + +func (i *Instance) Read(p []byte) (n int, err error) { + i.addEntropy() + return rand.New(rand.NewSource(i.seed)).Read(p) +} + +func (i *Instance) Shuffle(n int, swap func(i, j int)) { + i.addEntropy() + rand.New(rand.NewSource(i.seed)).Shuffle(n, swap) +} diff --git a/examples/gno.land/p/demo/rand/rand0_filetest.gno b/examples/gno.land/p/demo/rand/rand0_filetest.gno new file mode 100644 index 00000000000..446e04b696d --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand0_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/rand" +) + +func main() { + // initial + println("---") + r := rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + + // should be the same + println("---") + r = rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + + std.TestSkipHeights(1) + println("---") + r = rand.New() + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) + println(r.Intn(1000)) +} + +// Output: +// --- +// 777 +// 257 +// 74 +// 177 +// 802 +// --- +// 777 +// 257 +// 74 +// 177 +// 802 +// --- +// 269 +// 233 +// 591 +// 936 +// 908 diff --git a/examples/gno.land/p/demo/rand/rand_test.gno b/examples/gno.land/p/demo/rand/rand_test.gno new file mode 100644 index 00000000000..2651f0af089 --- /dev/null +++ b/examples/gno.land/p/demo/rand/rand_test.gno @@ -0,0 +1,49 @@ +package rand + +import ( + "fmt" + "std" + "strings" + "testing" + + "gno.land/p/demo/rand" +) + +func TestInstance(t *testing.T) { + instance := rand.New() + if instance == nil { + t.Errorf("instance should not be nil") + } +} + +func TestIntn(t *testing.T) { + baseRand := rand.New() + baseResult := computeIntn(t, baseRand) + + sameHeightRand := rand.New() + sameHeightResult := computeIntn(t, sameHeightRand) + + if baseResult != sameHeightResult { + t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) + } + + std.TestSkipHeights(1) + differentHeightRand := rand.New() + differentHeightResult := computeIntn(t, differentHeightRand) + + if baseResult == differentHeightResult { + t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) + } +} + +func computeIntn(t *testing.T, r *rand.Instance) string { + t.Helper() + + arr := []string{} + for i := 0; i < 10; i++ { + arr = append(arr, fmt.Sprintf("%d", r.Intn(1000))) + } + + out := strings.Join(arr, ",") + return out +} diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index f2d3ddebadc..6276629cba2 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,8 @@ +// Draft + module gno.land/r/demo/art/gnoface -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/rand v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 9e85c5c7387..95493b52bf5 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -1,16 +1,16 @@ package gnoface import ( - "math/rand" "std" "strconv" "strings" + "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := std.GetHeight() path = strings.TrimSpace(path) if path != "" { @@ -18,7 +18,7 @@ func Render(path string) string { if err != nil { panic(err) } - seed = uint64(s) + seed = int64(s) } output := ufmt.Sprintf("Gnoface #%d\n", seed) @@ -26,7 +26,7 @@ func Render(path string) string { return output } -func Draw(seed uint64) string { +func Draw(seed int64) string { var ( hairs = []string{ " s", @@ -102,7 +102,7 @@ func Draw(seed uint64) string { } ) - r := rand.New(rand.NewPCG(seed, 0xdeadbeef)) + r := rand.FromSeed(seed) return pick(r, hairs) + "\n" + pick(r, headtop) + "\n" + @@ -117,8 +117,8 @@ func Draw(seed uint64) string { pick(r, headbottom) + "\n" } -func pick(r *rand.Rand, slice []string) string { - return slice[r.IntN(len(slice))] +func pick(r *rand.Instance, slice []string) string { + return slice[r.Intn(len(slice))] } // based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index e82bd819483..630cce85c55 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno @@ -8,21 +8,21 @@ import ( func TestDraw(t *testing.T) { cases := []struct { - seed uint64 + seed int64 expected string }{ { seed: 42, expected: ` ||||||| - ||||||||| + ////////\ | | - | . ~ | -)| v v |O + | ~ . | +)| X X |. | | - | L | + | C | | | - | ___ | + | __/ | | | \~~~~~~~/ `[1:], @@ -30,31 +30,31 @@ func TestDraw(t *testing.T) { { seed: 1337, expected: ` - ....... - ||||||||| + s + /|||||||\ | | - | . _ | -D| x X |O + | . * | +o| ~ ~ |. | | - | ~ | + | O | | | - | ~~~ | + | __/ | | | - \~~~~~~~/ + \_______/ `[1:], }, { seed: 123456789, expected: ` - ....... - ////////\ + s + /~~~~~~~\ | | - | ~ * | -|| x X |o + | ~ . | +<| ~ ~ |< | | | V | | | - | . | + | \_/ | | | \-------/ `[1:], @@ -80,14 +80,14 @@ func TestRender(t *testing.T) { path: "42", expected: "Gnoface #42\n```" + ` ||||||| - ||||||||| + ////////\ | | - | . ~ | -)| v v |O + | ~ . | +)| X X |. | | - | L | + | C | | | - | ___ | + | __/ | | | \~~~~~~~/ ` + "```\n", @@ -95,31 +95,31 @@ func TestRender(t *testing.T) { { path: "1337", expected: "Gnoface #1337\n```" + ` - ....... - ||||||||| + s + /|||||||\ | | - | . _ | -D| x X |O + | . * | +o| ~ ~ |. | | - | ~ | + | O | | | - | ~~~ | + | __/ | | | - \~~~~~~~/ + \_______/ ` + "```\n", }, { path: "123456789", expected: "Gnoface #123456789\n```" + ` - ....... - ////////\ + s + /~~~~~~~\ | | - | ~ * | -|| x X |o + | ~ . | +<| ~ ~ |< | | | V | | | - | . | + | \_/ | | | \-------/ ` + "```\n", diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index 7044f0f72b3..9804aecc7f1 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -2,4 +2,7 @@ module gno.land/r/x/manfred_outfmt -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/rand v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno index 01981024189..5468a65c06f 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -2,9 +2,9 @@ package outfmt import ( "encoding/json" - "math/rand" "strings" + "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) @@ -27,21 +27,22 @@ func (res *Result) String() string { return output } -var rSeed = rand.NewPCG(0, 0) +var rSeed int64 func genResult() Result { - r := rand.New(rSeed) - // init rand + r := rand.FromSeed(rSeed) + defer func() { rSeed = r.Seed() }() + res := Result{ Text: "Hello Gnomes!", - Number: r.IntN(1000), + Number: r.Intn(1000), } - length := r.IntN(8) + 2 + length := r.Intn(8) + 2 res.Numbers = make([]int, length) for i := 0; i < length; i++ { - res.Numbers[i] = r.IntN(100) + res.Numbers[i] = r.Intn(100) } return res diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index 69c07bbbf16..a60aeb384db 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -15,7 +15,7 @@ func TestRender(t *testing.T) { * [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -23,11 +23,11 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 222 -Numbers: 34 44 39 7 72 48 74 +Number: 957 +Numbers: 3 54 32 88 ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -35,11 +35,11 @@ Numbers: 34 44 39 7 72 48 74 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 898 -Numbers: 24 25 2 +Number: 141 +Numbers: 98 27 ` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } @@ -47,18 +47,18 @@ Numbers: 24 25 2 { got := outfmt.Render("?fmt=json") - expected := `{"Number":746,"Text":"Hello Gnomes!","Numbers":[57,82,16,14,28,32]}` + expected := `{"Number":801,"Text":"Hello Gnomes!","Numbers":[5,78,51,78,91,41]}` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":795,"Text":"Hello Gnomes!","Numbers":[29,51,88,61,93,21,2,66,79]})` + expected := `callback({"Number":63,"Text":"Hello Gnomes!","Numbers":[2,66,50,73,81]})` if got != expected { - t.Errorf("expected %q, got %q.", expected, got) + t.Fatalf("expected %q, got %q.", expected, got) } } } diff --git a/gnovm/stdlibs/bytes/buffer_test.gno b/gnovm/stdlibs/bytes/buffer_test.gno index 601901955cd..a8837494224 100644 --- a/gnovm/stdlibs/bytes/buffer_test.gno +++ b/gnovm/stdlibs/bytes/buffer_test.gno @@ -216,14 +216,14 @@ func TestMixedReadsAndWrites(t *testing.T) { var buf bytes.Buffer s := "" for i := 0; i < 50; i++ { - wlen := rand.IntN(len(testString)) + wlen := rand.Intn(len(testString)) if i%2 == 0 { s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen]) } else { s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen]) } - rlen := rand.IntN(len(testString)) + rlen := rand.Intn(len(testString)) fub := make([]byte, rlen) n, _ := buf.Read(fub) s = s[n:] diff --git a/gnovm/stdlibs/bytes/bytes_test.gno b/gnovm/stdlibs/bytes/bytes_test.gno index 927f89c5559..c7762f2f67b 100644 --- a/gnovm/stdlibs/bytes/bytes_test.gno +++ b/gnovm/stdlibs/bytes/bytes_test.gno @@ -1707,7 +1707,7 @@ var makeFieldsInput = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space. for i := range x { - switch rand.IntN(10) { + switch rand.Intn(10) { case 0: x[i] = ' ' case 1: @@ -1729,7 +1729,7 @@ var makeFieldsInputASCII = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, rest ASCII non-space. for i := range x { - if rand.IntN(10) == 0 { + if rand.Intn(10) == 0 { x[i] = ' ' } else { x[i] = 'x' @@ -1827,7 +1827,7 @@ func makeBenchInputHard() []byte { } x := make([]byte, 0, 1<<20) for { - i := rand.IntN(len(tokens)) + i := rand.Intn(len(tokens)) if len(x)+len(tokens[i]) >= 1<<20 { break } diff --git a/gnovm/stdlibs/math/rand/auto_test.gno b/gnovm/stdlibs/math/rand/auto_test.gno deleted file mode 100644 index 5945039ae18..00000000000 --- a/gnovm/stdlibs/math/rand/auto_test.gno +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rand - -import ( - "testing" -) - -// This test is first, in its own file with an alphabetically early name, -// to try to make sure that it runs early. It has the best chance of -// detecting deterministic seeding if it's the first test that runs. - -func TestAuto(t *testing.T) { - // Pull out 10 int64s from the global source - // and then check that they don't appear in that - // order in the deterministic seeded result. - var out []int64 - for i := 0; i < 10; i++ { - out = append(out, Int64()) - } - - // Look for out in seeded output. - // Strictly speaking, we should look for them in order, - // but this is good enough and not significantly more - // likely to have a false positive. - r := New(NewPCG(1, 0)) - found := 0 - for i := 0; i < 1000; i++ { - x := r.Int64() - if x == out[found] { - found++ - if found == len(out) { - t.Fatalf("found unseeded output in Seed(1) output") - } - } - } -} diff --git a/gnovm/stdlibs/math/rand/pcg.gno b/gnovm/stdlibs/math/rand/pcg.gno deleted file mode 100644 index 77708d799e2..00000000000 --- a/gnovm/stdlibs/math/rand/pcg.gno +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rand - -import ( - "errors" - "math/bits" -) - -// https://numpy.org/devdocs/reference/random/upgrading-pcg64.html -// https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005 - -// A PCG is a PCG generator with 128 bits of internal state. -// A zero PCG is equivalent to NewPCG(0, 0). -type PCG struct { - hi uint64 - lo uint64 -} - -// NewPCG returns a new PCG seeded with the given values. -func NewPCG(seed1, seed2 uint64) *PCG { - return &PCG{seed1, seed2} -} - -// Seed resets the PCG to behave the same way as NewPCG(seed1, seed2). -func (p *PCG) Seed(seed1, seed2 uint64) { - p.hi = seed1 - p.lo = seed2 -} - -// binary.bigEndian.Uint64, copied to avoid dependency -func beUint64(b []byte) uint64 { - _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 - 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 -} - -// binary.bigEndian.PutUint64, copied to avoid dependency -func bePutUint64(b []byte, v uint64) { - _ = b[7] // early bounds check to guarantee safety of writes below - 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) -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (p *PCG) MarshalBinary() ([]byte, error) { - b := make([]byte, 20) - copy(b, "pcg:") - bePutUint64(b[4:], p.hi) - bePutUint64(b[4+8:], p.lo) - return b, nil -} - -var errUnmarshalPCG = errors.New("invalid PCG encoding") - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. -func (p *PCG) UnmarshalBinary(data []byte) error { - if len(data) != 20 || string(data[:4]) != "pcg:" { - return errUnmarshalPCG - } - p.hi = beUint64(data[4:]) - p.lo = beUint64(data[4+8:]) - return nil -} - -func (p *PCG) next() (hi, lo uint64) { - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161 - // - // Numpy's PCG multiplies by the 64-bit value cheapMul - // instead of the 128-bit value used here and in the official PCG code. - // This does not seem worthwhile, at least for Go: not having any high - // bits in the multiplier reduces the effect of low bits on the highest bits, - // and it only saves 1 multiply out of 3. - // (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.) - const ( - mulHi = 2549297995355413924 - mulLo = 4865540595714422341 - incHi = 6364136223846793005 - incLo = 1442695040888963407 - ) - - // state = state * mul + inc - hi, lo = bits.Mul64(p.lo, mulLo) - hi += p.hi*mulLo + p.lo*mulHi - lo, c := bits.Add64(lo, incLo, 0) - hi, _ = bits.Add64(hi, incHi, c) - p.lo = lo - p.hi = hi - return hi, lo -} - -// Uint64 return a uniformly-distributed random uint64 value. -func (p *PCG) Uint64() uint64 { - hi, lo := p.next() - - // XSL-RR would be - // hi, lo := p.next() - // return bits.RotateLeft64(lo^hi, -int(hi>>58)) - // but Numpy uses DXSM and O'Neill suggests doing the same. - // See https://github.com/golang/go/issues/21835#issuecomment-739065688 - // and following comments. - - // DXSM "double xorshift multiply" - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015 - - // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176 - const cheapMul = 0xda942042e4dd58b5 - hi ^= hi >> 32 - hi *= cheapMul - hi ^= hi >> 48 - hi *= (lo | 1) - return hi -} diff --git a/gnovm/stdlibs/math/rand/pcg_test.gno b/gnovm/stdlibs/math/rand/pcg_test.gno deleted file mode 100644 index 843506d7234..00000000000 --- a/gnovm/stdlibs/math/rand/pcg_test.gno +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rand - -import ( - "testing" -) - -func BenchmarkPCG_DXSM(b *testing.B) { - var p PCG - var t uint64 - for n := b.N; n > 0; n-- { - t += p.Uint64() - } - Sink = t -} - -func TestPCGMarshal(t *testing.T) { - var p PCG - const ( - seed1 = 0x123456789abcdef0 - seed2 = 0xfedcba9876543210 - want = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10" - ) - p.Seed(seed1, seed2) - data, err := p.MarshalBinary() - if string(data) != want || err != nil { - t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want) - } - - q := PCG{} - if err := q.UnmarshalBinary([]byte(want)); err != nil { - t.Fatalf("UnmarshalBinary(): %v", err) - } - if q != p { - t.Fatalf("after round trip, q = %#x, but p = %#x", q, p) - } - - qu := q.Uint64() - pu := p.Uint64() - if qu != pu { - t.Errorf("after round trip, q.Uint64() = %#x, but p.Uint64() = %#x", qu, pu) - } -} - -func TestPCG(t *testing.T) { - p := NewPCG(1, 2) - want := []uint64{ - 0xc4f5a58656eef510, - 0x9dcec3ad077dec6c, - 0xc8d04605312f8088, - 0xcbedc0dcb63ac19a, - 0x3bf98798cae97950, - 0xa8c6d7f8d485abc, - 0x7ffa3780429cd279, - 0x730ad2626b1c2f8e, - 0x21ff2330f4a0ad99, - 0x2f0901a1947094b0, - 0xa9735a3cfbe36cef, - 0x71ddb0a01a12c84a, - 0xf0e53e77a78453bb, - 0x1f173e9663be1e9d, - 0x657651da3ac4115e, - 0xc8987376b65a157b, - 0xbb17008f5fca28e7, - 0x8232bd645f29ed22, - 0x12be8f07ad14c539, - 0x54908a48e8e4736e, - } - - for i, x := range want { - if u := p.Uint64(); u != x { - t.Errorf("PCG #%d = %#x, want %#x", i, u, x) - } - } -} diff --git a/gnovm/stdlibs/math/rand/rand.gno b/gnovm/stdlibs/math/rand/rand.gno deleted file mode 100644 index 250ea7c5a05..00000000000 --- a/gnovm/stdlibs/math/rand/rand.gno +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package rand implements pseudo-random number generators suitable for tasks -// such as simulation, but it should not be used for security-sensitive work. -// -// IMPORTANT NOTE: This does not equate to Go's math/rand; instead, it uses -// the implementation in math/rand/v2, which improves on its algorithm and -// usability. -// -// Random numbers are generated by a [Source], usually wrapped in a [Rand]. -// Both types should be used by a single goroutine at a time: sharing among -// multiple goroutines requires some kind of synchronization. -// -// Top-level functions, such as [Float64] and [Int], -// are safe for concurrent use by multiple goroutines. -// -// This package's outputs might be easily predictable regardless of how it's -// seeded. -package rand - -import ( - "math/bits" -) - -// A Source is a source of uniformly-distributed -// pseudo-random uint64 values in the range [0, 1<<64). -// -// A Source is not safe for concurrent use by multiple goroutines. -type Source interface { - Uint64() uint64 -} - -// A Rand is a source of random numbers. -type Rand struct { - src Source -} - -// New returns a new Rand that uses random values from src -// to generate other random values. -func New(src Source) *Rand { - return &Rand{src: src} -} - -// Int64 returns a non-negative pseudo-random 63-bit integer as an int64. -func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) } - -// Uint32 returns a pseudo-random 32-bit value as a uint32. -func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) } - -// Uint64 returns a pseudo-random 64-bit value as a uint64. -func (r *Rand) Uint64() uint64 { return r.src.Uint64() } - -// Int32 returns a non-negative pseudo-random 31-bit integer as an int32. -func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) } - -// Int returns a non-negative pseudo-random int. -func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) } - -// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) Int64N(n int64) int64 { - if n <= 0 { - panic("invalid argument to Int64N") - } - return int64(r.uint64n(uint64(n))) -} - -// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) Uint64N(n uint64) uint64 { - if n == 0 { - panic("invalid argument to Uint64N") - } - return r.uint64n(n) -} - -// uint64n is the no-bounds-checks version of Uint64N. -func (r *Rand) uint64n(n uint64) uint64 { - if is32bit && uint64(uint32(n)) == n { - return uint64(r.uint32n(uint32(n))) - } - if n&(n-1) == 0 { // n is power of two, can mask - return r.Uint64() & (n - 1) - } - - // Suppose we have a uint64 x uniform in the range [0,2⁶⁴) - // and want to reduce it to the range [0,n) preserving exact uniformity. - // We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by - // the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴. - // Since there are 2⁶⁴ possible inputs x and only n possible outputs, - // the output is necessarily biased if n does not divide 2⁶⁴. - // In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴). - // There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products - // in that range, depending on k. - // But suppose we reject the sample and try again when - // x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible - // outcomes out of the 2⁶⁴. - // Now there are exactly floor(2⁶⁴/n) possible ways to produce - // each output value k, so we've restored uniformity. - // To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n, - // so the direct implementation of this algorithm would be: - // - // hi, lo := bits.Mul64(r.Uint64(), n) - // thresh := -n % n - // for lo < thresh { - // hi, lo = bits.Mul64(r.Uint64(), n) - // } - // - // That still leaves an expensive 64-bit division that we would rather avoid. - // We know that thresh < n, and n is usually much less than 2⁶⁴, so we can - // avoid the last four lines unless lo < n. - // - // See also: - // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction - // https://lemire.me/blog/2016/06/30/fast-random-shuffling - hi, lo := bits.Mul64(r.Uint64(), n) - if lo < n { - thresh := -n % n - for lo < thresh { - hi, lo = bits.Mul64(r.Uint64(), n) - } - } - return hi -} - -// uint32n is an identical computation to uint64n -// but optimized for 32-bit systems. -func (r *Rand) uint32n(n uint32) uint32 { - if n&(n-1) == 0 { // n is power of two, can mask - return uint32(r.Uint64()) & (n - 1) - } - // On 64-bit systems we still use the uint64 code below because - // the probability of a random uint64 lo being < a uint32 n is near zero, - // meaning the unbiasing loop almost never runs. - // On 32-bit systems, here we need to implement that same logic in 32-bit math, - // both to preserve the exact output sequence observed on 64-bit machines - // and to preserve the optimization that the unbiasing loop almost never runs. - // - // We want to compute - // hi, lo := bits.Mul64(r.Uint64(), n) - // In terms of 32-bit halves, this is: - // x1:x0 := r.Uint64() - // 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n) - // Writing out the multiplication in terms of bits.Mul32 allows - // using direct hardware instructions and avoiding - // the computations involving these zeros. - x := r.Uint64() - lo1a, lo0 := bits.Mul32(uint32(x), n) - hi, lo1b := bits.Mul32(uint32(x>>32), n) - lo1, c := bits.Add32(lo1a, lo1b, 0) - hi += c - if lo1 == 0 && lo0 < uint32(n) { - n64 := uint64(n) - thresh := uint32(-n64 % n64) - for lo1 == 0 && lo0 < thresh { - x := r.Uint64() - lo1a, lo0 = bits.Mul32(uint32(x), n) - hi, lo1b = bits.Mul32(uint32(x>>32), n) - lo1, c = bits.Add32(lo1a, lo1b, 0) - hi += c - } - } - return hi -} - -// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) Int32N(n int32) int32 { - if n <= 0 { - panic("invalid argument to Int32N") - } - return int32(r.uint64n(uint64(n))) -} - -// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) Uint32N(n uint32) uint32 { - if n == 0 { - panic("invalid argument to Uint32N") - } - return uint32(r.uint64n(uint64(n))) -} - -const is32bit = ^uint(0)>>32 == 0 - -// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n <= 0. -func (r *Rand) IntN(n int) int { - if n <= 0 { - panic("invalid argument to IntN") - } - return int(r.uint64n(uint64(n))) -} - -// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n). -// It panics if n == 0. -func (r *Rand) UintN(n uint) uint { - if n == 0 { - panic("invalid argument to UintN") - } - return uint(r.uint64n(uint64(n))) -} - -// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). -func (r *Rand) Float64() float64 { - // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53). - return float64(r.Uint64()<<11>>11) / (1 << 53) -} - -// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). -func (r *Rand) Float32() float32 { - // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24). - return float32(r.Uint32()<<8>>8) / (1 << 24) -} - -// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers -// in the half-open interval [0,n). -func (r *Rand) Perm(n int) []int { - p := make([]int, n) - for i := range p { - p[i] = i - } - r.Shuffle(len(p), func(i, j int) { p[i], p[j] = p[j], p[i] }) - return p -} - -// Shuffle pseudo-randomizes the order of elements. -// n is the number of elements. Shuffle panics if n < 0. -// swap swaps the elements with indexes i and j. -func (r *Rand) Shuffle(n int, swap func(i, j int)) { - if n < 0 { - panic("invalid argument to Shuffle") - } - - // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle - // Shuffle really ought not be called with n that doesn't fit in 32 bits. - // Not only will it take a very long time, but with 2³¹! possible permutations, - // there's no way that any PRNG can have a big enough internal state to - // generate even a minuscule percentage of the possible permutations. - // Nevertheless, the right API signature accepts an int n, so handle it as best we can. - for i := n - 1; i > 0; i-- { - j := int(r.uint64n(uint64(i + 1))) - swap(i, j) - } -} - -/* - * Top-level convenience functions - */ - -// globalRand is the source of random numbers for the top-level -// convenience functions. -var globalRand = &Rand{src: &PCG{}} - -// Int64 returns a non-negative pseudo-random 63-bit integer as an int64 -// from the default Source. -func Int64() int64 { return globalRand.Int64() } - -// Uint32 returns a pseudo-random 32-bit value as a uint32 -// from the default Source. -func Uint32() uint32 { return globalRand.Uint32() } - -// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) } - -// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) } - -// Uint64 returns a pseudo-random 64-bit value as a uint64 -// from the default Source. -func Uint64() uint64 { return globalRand.Uint64() } - -// Int32 returns a non-negative pseudo-random 31-bit integer as an int32 -// from the default Source. -func Int32() int32 { return globalRand.Int32() } - -// Int returns a non-negative pseudo-random int from the default Source. -func Int() int { return globalRand.Int() } - -// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Int64N(n int64) int64 { return globalRand.Int64N(n) } - -// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func Int32N(n int32) int32 { return globalRand.Int32N(n) } - -// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func IntN(n int) int { return globalRand.IntN(n) } - -// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n) -// from the default Source. -// It panics if n <= 0. -func UintN(n uint) uint { return globalRand.UintN(n) } - -// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) -// from the default Source. -func Float64() float64 { return globalRand.Float64() } - -// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) -// from the default Source. -func Float32() float32 { return globalRand.Float32() } - -// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers -// in the half-open interval [0,n) from the default Source. -func Perm(n int) []int { return globalRand.Perm(n) } - -// Shuffle pseudo-randomizes the order of elements using the default Source. -// n is the number of elements. Shuffle panics if n < 0. -// swap swaps the elements with indexes i and j. -func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } - -// NormFloat64 returns a normally distributed float64 in the range -// [-math.MaxFloat64, +math.MaxFloat64] with -// standard normal distribution (mean = 0, stddev = 1) -// from the default Source. -// To produce a different normal distribution, callers can -// adjust the output using: -// -// sample = NormFloat64() * desiredStdDev + desiredMean -func NormFloat64() float64 { return globalRand.NormFloat64() } - -// ExpFloat64 returns an exponentially distributed float64 in the range -// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter -// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. -// To produce a distribution with a different rate parameter, -// callers can adjust the output using: -// -// sample = ExpFloat64() / desiredRateParameter -func ExpFloat64() float64 { return globalRand.ExpFloat64() } diff --git a/gnovm/stdlibs/math/rand/zipf.gno b/gnovm/stdlibs/math/rand/zipf.gno deleted file mode 100644 index f04c814eb75..00000000000 --- a/gnovm/stdlibs/math/rand/zipf.gno +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// W.Hormann, G.Derflinger: -// "Rejection-Inversion to Generate Variates -// from Monotone Discrete Distributions" -// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz - -package rand - -import "math" - -// A Zipf generates Zipf distributed variates. -type Zipf struct { - r *Rand - imax float64 - v float64 - q float64 - s float64 - oneminusQ float64 - oneminusQinv float64 - hxm float64 - hx0minusHxm float64 -} - -func (z *Zipf) h(x float64) float64 { - return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv -} - -func (z *Zipf) hinv(x float64) float64 { - return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v -} - -// NewZipf returns a Zipf variate generator. -// The generator generates values k ∈ [0, imax] -// such that P(k) is proportional to (v + k) ** (-s). -// Requirements: s > 1 and v >= 1. -func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { - z := new(Zipf) - if s <= 1.0 || v < 1 { - return nil - } - z.r = r - z.imax = float64(imax) - z.v = v - z.q = s - z.oneminusQ = 1.0 - z.q - z.oneminusQinv = 1.0 / z.oneminusQ - z.hxm = z.h(z.imax + 0.5) - z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm - z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) - return z -} - -// Uint64 returns a value drawn from the Zipf distribution described -// by the Zipf object. -func (z *Zipf) Uint64() uint64 { - if z == nil { - panic("rand: nil Zipf") - } - k := 0.0 - - for { - r := z.r.Float64() // r on [0,1] - ur := z.hxm + r*z.hx0minusHxm - x := z.hinv(ur) - k = math.Floor(x + 0.5) - if k-x <= z.s { - break - } - if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { - break - } - } - return uint64(k) -} diff --git a/gnovm/stdlibs/sort/sort_test.gno b/gnovm/stdlibs/sort/sort_test.gno index dac6ab11f67..8416f0b77e1 100644 --- a/gnovm/stdlibs/sort/sort_test.gno +++ b/gnovm/stdlibs/sort/sort_test.gno @@ -98,7 +98,7 @@ func TestSortLarge_Random(t *testing.T) { } data := make([]int, n) for i := 0; i < len(data); i++ { - data[i] = rand.IntN(100) + data[i] = rand.Intn(100) } if sort.IntsAreSorted(data) { t.Fatalf("terrible rand.rand") @@ -160,7 +160,7 @@ func TestNonDeterministicComparison(t *testing.T) { }() td := &nonDeterministicTestingData{ - r: rand.New(rand.NewPCG(0, 0)), + r: rand.New(rand.NewSource(0)), } for i := 0; i < 10; i++ { @@ -377,13 +377,13 @@ func testBentleyMcIlroy(t *testing.T, sortFn func(sort.Interface), maxswap func( case _Sawtooth: data[i] = i % m case _Rand: - data[i] = rand.IntN(m) + data[i] = rand.Intn(m) case _Stagger: data[i] = (i*m + i) % n case _Plateau: data[i] = min(i, m) case _Shuffle: - if rand.IntN(m) != 0 { + if rand.Intn(m) != 0 { j += 2 data[i] = j } else { @@ -587,7 +587,7 @@ func TestStability(t *testing.T) { // random distribution for i := 0; i < len(data); i++ { - data[i].a = rand.IntN(m) + data[i].a = rand.Intn(m) } if sort.IsSorted(data) { t.Fatalf("terrible rand.rand") @@ -643,7 +643,7 @@ func countOps(t *testing.T, algo func(sort.Interface), name string) { maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { - td.data[i] = rand.IntN(n / 5) + td.data[i] = rand.Intn(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) diff --git a/gnovm/tests/files/extern/p1/s2.gno b/gnovm/tests/files/extern/p1/s2.gno index 01aa3f47da1..cfed6498051 100644 --- a/gnovm/tests/files/extern/p1/s2.gno +++ b/gnovm/tests/files/extern/p1/s2.gno @@ -3,3 +3,5 @@ package p1 import "math/rand" var Uint32 = rand.Uint32 + +func init() { rand.Seed(1) } diff --git a/gnovm/tests/files/import4.gno b/gnovm/tests/files/import4.gno index 91364f45389..dc52adafc31 100644 --- a/gnovm/tests/files/import4.gno +++ b/gnovm/tests/files/import4.gno @@ -5,4 +5,4 @@ import "github.com/gnolang/gno/_test/p1" func main() { println("num:", p1.Uint32()) } // Output: -// num: 956301160 +// num: 2596996162 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 86c81be9a18..3dbd292ea68 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -23,7 +23,7 @@ import ( "log" "math" "math/big" - "math/rand/v2" + "math/rand" "net" "net/url" "os" @@ -114,6 +114,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || pkgPath == "math/big" || + pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || mode == ImportModeNativePreferred { switch pkgPath { @@ -270,10 +271,12 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri case "math/rand": // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) - // make native rand same as gno rand. - rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec - pkg.DefineGoNativeValue("IntN", rnd.IntN) - pkg.DefineGoNativeValue("Uint32", rnd.Uint32) + pkg.DefineGoNativeValue("Intn", rand.Intn) + pkg.DefineGoNativeValue("Uint32", rand.Uint32) + pkg.DefineGoNativeValue("Seed", rand.Seed) + pkg.DefineGoNativeValue("New", rand.New) + pkg.DefineGoNativeValue("NewSource", rand.NewSource) + pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) return pkg, pkg.NewPackage() case "crypto/rand": pkg := gno.NewPackageNode("rand", pkgPath, nil) diff --git a/go.mod b/go.mod index d2b4a5f9825..30a7f4dd84c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 12297e3c6ca..0236341ccc3 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,6 +1,6 @@ module loop -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index e0cb385020e..25d9a218cf7 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/misc/devdeps -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index be771c9a952..b92418db3dd 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,6 +1,6 @@ module linter -go 1.22 +go 1.21.6 toolchain go1.22.4 diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 91d0d6cdf34..4bb42f103e4 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,6 +1,6 @@ module loop -go 1.22 +go 1.21 toolchain go1.22.4 diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md index bf58440e85e..d55a4cad305 100644 --- a/tm2/pkg/libtm/README.md +++ b/tm2/pkg/libtm/README.md @@ -45,7 +45,7 @@ To get up and running with the `libtm` package, you can add it to your project u go get -u github.com/gnolang/libtm ``` -Currently, the minimum required go version is `go 1.22`. +Currently, the minimum required go version is `go 1.21`. ## Usage Examples diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod index e903dd664e6..a8f80c76e06 100644 --- a/tm2/pkg/libtm/go.mod +++ b/tm2/pkg/libtm/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/libtm -go 1.22 +go 1.21 toolchain go1.22.4 From fadcbe100153fc6679cfbaf05549819ee6449a12 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 20:56:06 +0200 Subject: [PATCH 2/6] chore: reapply f547d7dcac11de7937664413497fed35eb1a03ff partially Signed-off-by: moul <94029+moul@users.noreply.github.com> --- .github/workflows/docs-linter.yml | 4 +- .github/workflows/examples.yml | 5 +- CONTRIBUTING.md | 2 +- contribs/gnodev/go.mod | 2 +- contribs/gnofaucet/go.mod | 2 +- contribs/gnokeykc/go.mod | 2 +- contribs/gnomd/go.mod | 2 +- .../local-setup/installation.md | 16 +- .../validators/connect-to-existing-chain.md | 4 +- .../validators/setting-up-a-new-chain.md | 4 +- docs/reference/go-gno-compatibility.md | 5 +- examples/gno.land/r/demo/art/gnoface/gno.mod | 7 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 14 +- .../r/demo/art/gnoface/gnoface_test.gno | 70 ++-- examples/gno.land/r/x/manfred_outfmt/gno.mod | 5 +- .../gno.land/r/x/manfred_outfmt/outfmt.gno | 15 +- .../r/x/manfred_outfmt/outfmt_test.gno | 22 +- gnovm/stdlibs/bytes/buffer_test.gno | 4 +- gnovm/stdlibs/bytes/bytes_test.gno | 6 +- gnovm/stdlibs/math/rand/auto_test.gno | 39 ++ gnovm/stdlibs/math/rand/pcg.gno | 121 +++++++ gnovm/stdlibs/math/rand/pcg_test.gno | 78 ++++ gnovm/stdlibs/math/rand/rand.gno | 340 ++++++++++++++++++ gnovm/stdlibs/math/rand/zipf.gno | 77 ++++ gnovm/stdlibs/sort/sort_test.gno | 12 +- gnovm/tests/files/extern/p1/s2.gno | 2 - gnovm/tests/files/import4.gno | 2 +- gnovm/tests/imports.go | 13 +- go.mod | 2 +- misc/autocounterd/go.mod | 2 +- misc/devdeps/go.mod | 2 +- misc/docs-linter/go.mod | 2 +- misc/loop/go.mod | 2 +- tm2/pkg/libtm/README.md | 2 +- tm2/pkg/libtm/go.mod | 2 +- 35 files changed, 764 insertions(+), 125 deletions(-) create mode 100644 gnovm/stdlibs/math/rand/auto_test.gno create mode 100644 gnovm/stdlibs/math/rand/pcg.gno create mode 100644 gnovm/stdlibs/math/rand/pcg_test.gno create mode 100644 gnovm/stdlibs/math/rand/rand.gno create mode 100644 gnovm/stdlibs/math/rand/zipf.gno diff --git a/.github/workflows/docs-linter.yml b/.github/workflows/docs-linter.yml index 0ffa67dfe95..e56c1a663ea 100644 --- a/.github/workflows/docs-linter.yml +++ b/.github/workflows/docs-linter.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: '1.21' + go-version: '1.22' - name: Install dependencies run: go mod download @@ -28,4 +28,4 @@ jobs: run: make -C docs/ build - name: Run linter - run: make -C docs/ lint \ No newline at end of file + run: make -C docs/ lint diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index cea52e74cd4..d11710344b1 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -20,7 +20,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" runs-on: ubuntu-latest timeout-minutes: 30 @@ -36,7 +35,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -60,7 +58,6 @@ jobs: fail-fast: false matrix: goversion: - - "1.21.x" - "1.22.x" # unittests: TODO: matrix with contracts runs-on: ubuntu-latest @@ -108,4 +105,4 @@ jobs: # Find all directories containing gno.mod file find ./examples -name "gno.mod" -execdir go run "$GNO_CMD" mod tidy \; # Check if there are changes after running gno mod tidy - git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) \ No newline at end of file + git diff --exit-code || (echo "Some gno.mod files are not tidy, please run 'make tidy'." && exit 1) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e041ab18875..bc125a6da73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ The gno repository is primarily based on Go (Golang) and Gno. The primary tech stack for working on the repository: -- Go (version 1.21+) +- Go (version 1.22+) - make (for using Makefile configurations) It is recommended to work on a Unix environment, as most of the tooling is built around ready-made tools in Unix (WSL2 diff --git a/contribs/gnodev/go.mod b/contribs/gnodev/go.mod index ec82f09e467..6d2c7a34293 100644 --- a/contribs/gnodev/go.mod +++ b/contribs/gnodev/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnodev -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnofaucet/go.mod b/contribs/gnofaucet/go.mod index dc5267fff2b..e4b63c7a9e9 100644 --- a/contribs/gnofaucet/go.mod +++ b/contribs/gnofaucet/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnofaucet -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnokeykc/go.mod b/contribs/gnokeykc/go.mod index d9e785f5226..711cafed241 100644 --- a/contribs/gnokeykc/go.mod +++ b/contribs/gnokeykc/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnokeykc -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/contribs/gnomd/go.mod b/contribs/gnomd/go.mod index 39043bae144..8bc352d4848 100644 --- a/contribs/gnomd/go.mod +++ b/contribs/gnomd/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/contribs/gnomd -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/docs/getting-started/local-setup/installation.md b/docs/getting-started/local-setup/installation.md index 8700ff9a2b2..a3658fa6ab3 100644 --- a/docs/getting-started/local-setup/installation.md +++ b/docs/getting-started/local-setup/installation.md @@ -5,15 +5,15 @@ id: installation # Installation ## Overview -In this tutorial, you will learn how to set up the Gno development environment -locally, so you can get up and running writing Gno code. You will download and +In this tutorial, you will learn how to set up the Gno development environment +locally, so you can get up and running writing Gno code. You will download and install all the necessary tooling, and validate that it is correctly configured to run on your machine. ## Prerequisites - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: - Make sure `$GOPATH` is well-defined, and `$GOPATH/bin` is added to your `$PATH` variable. - To do this, you can add the following line to your `.bashrc`, `.zshrc` or other config file: @@ -30,7 +30,7 @@ GitHub repository somewhere on disk: git clone https://github.com/gnolang/gno.git ``` -## 2. Installing the required tools +## 2. Installing the required tools There are three tools that should be used for getting started with Gno development: - `gno` - the GnoVM binary @@ -42,7 +42,7 @@ To install all three tools, simply run the following in the root of the repo: make install ``` -## 3. Verifying installation +## 3. Verifying installation ### `gno` `gno` provides ample functionality to the user, among which is running, @@ -59,7 +59,7 @@ You should get the help output from the command: ![gno help](../../assets/getting-started/local-setup/local-setup/gno-help.gif) -Alternatively, if you don't want to have the binary callable system-wide, you +Alternatively, if you don't want to have the binary callable system-wide, you can run the binary directly: ```bash @@ -68,8 +68,8 @@ go run ./cmd/gno --help ``` ### `gnodev` -`gnodev` is the go-to Gno development helper tool - it comes with a built in -Gno.land node, a `gnoweb` server to display the state of your smart contracts +`gnodev` is the go-to Gno development helper tool - it comes with a built in +Gno.land node, a `gnoweb` server to display the state of your smart contracts (realms), and a watcher system to actively track changes in your code. Read more about `gnodev` [here](../../gno-tooling/cli/gnodev.md). diff --git a/docs/gno-infrastructure/validators/connect-to-existing-chain.md b/docs/gno-infrastructure/validators/connect-to-existing-chain.md index a1a337a5a48..f1acf06049f 100644 --- a/docs/gno-infrastructure/validators/connect-to-existing-chain.md +++ b/docs/gno-infrastructure/validators/connect-to-existing-chain.md @@ -12,7 +12,7 @@ In this tutorial, you will learn how to start a local Gno node and connect to an - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -107,4 +107,4 @@ gnoland start \ That's it! 🎉 -Your new Gno node should be up and running, and syncing block data from the remote chain. \ No newline at end of file +Your new Gno node should be up and running, and syncing block data from the remote chain. diff --git a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md index 1927679db8f..8f94037dc1d 100644 --- a/docs/gno-infrastructure/validators/setting-up-a-new-chain.md +++ b/docs/gno-infrastructure/validators/setting-up-a-new-chain.md @@ -13,7 +13,7 @@ Additionally, you will see the different options you can use to make your Gno in - **Git** - **`make` (for running Makefiles)** -- **Go 1.21+** +- **Go 1.22+** - **Go Environment Setup**: Ensure you have Go set up as outlined in the [Go official installation documentation](https://go.dev/doc/install) for your environment @@ -451,4 +451,4 @@ Genesis block generation happens only once during the lifetime of a Gno chain. This means that if you specify a balances file using `gnoland start`, and the chain has already started (advanced from block 0), the specified balance sheet will not be applied. -::: \ No newline at end of file +::: diff --git a/docs/reference/go-gno-compatibility.md b/docs/reference/go-gno-compatibility.md index 89ad4f7b990..a2f83f2bbc6 100644 --- a/docs/reference/go-gno-compatibility.md +++ b/docs/reference/go-gno-compatibility.md @@ -34,7 +34,7 @@ id: go-gno-compatibility Generics are currently not implemented. -Note that Gno does not support shadowing of built-in types. +Note that Gno does not support shadowing of built-in types. While the following built-in typecasting assignment would work in Go, this is not supported in Gno. ```go @@ -205,7 +205,7 @@ Legend: | math/big | `tbd` | | math/bits | `full` | | math/cmplx | `tbd` | -| math/rand | `todo` | +| math/rand | `full`[^9] | | mime | `tbd` | | mime/multipart | `tbd` | | mime/quotedprintable | `tbd` | @@ -291,6 +291,7 @@ Legend: determinism. Concurrent functionality (such as `time.Ticker`) is not implemented. [^8]: `crypto/ed25519` is currently only implemented for `Verify`, which should still cover a majority of use cases. A full implementation is welcome. +[^9]: `math/rand` in Gno ports over Go's `math/rand/v2`. ## Tooling (`gno` binary) diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index 6276629cba2..f2d3ddebadc 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,8 +1,3 @@ -// Draft - module gno.land/r/demo/art/gnoface -require ( - gno.land/p/demo/rand v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 95493b52bf5..9e85c5c7387 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -1,16 +1,16 @@ package gnoface import ( + "math/rand" "std" "strconv" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := std.GetHeight() + seed := uint64(std.GetHeight()) path = strings.TrimSpace(path) if path != "" { @@ -18,7 +18,7 @@ func Render(path string) string { if err != nil { panic(err) } - seed = int64(s) + seed = uint64(s) } output := ufmt.Sprintf("Gnoface #%d\n", seed) @@ -26,7 +26,7 @@ func Render(path string) string { return output } -func Draw(seed int64) string { +func Draw(seed uint64) string { var ( hairs = []string{ " s", @@ -102,7 +102,7 @@ func Draw(seed int64) string { } ) - r := rand.FromSeed(seed) + r := rand.New(rand.NewPCG(seed, 0xdeadbeef)) return pick(r, hairs) + "\n" + pick(r, headtop) + "\n" + @@ -117,8 +117,8 @@ func Draw(seed int64) string { pick(r, headbottom) + "\n" } -func pick(r *rand.Instance, slice []string) string { - return slice[r.Intn(len(slice))] +func pick(r *rand.Rand, slice []string) string { + return slice[r.IntN(len(slice))] } // based on https://github.com/moul/pipotron/blob/master/dict/ascii-face.yml diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno index 630cce85c55..e82bd819483 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface_test.gno @@ -8,21 +8,21 @@ import ( func TestDraw(t *testing.T) { cases := []struct { - seed int64 + seed uint64 expected string }{ { seed: 42, expected: ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ `[1:], @@ -30,31 +30,31 @@ func TestDraw(t *testing.T) { { seed: 1337, expected: ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ `[1:], }, { seed: 123456789, expected: ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ `[1:], @@ -80,14 +80,14 @@ func TestRender(t *testing.T) { path: "42", expected: "Gnoface #42\n```" + ` ||||||| - ////////\ + ||||||||| | | - | ~ . | -)| X X |. + | . ~ | +)| v v |O | | - | C | + | L | | | - | __/ | + | ___ | | | \~~~~~~~/ ` + "```\n", @@ -95,31 +95,31 @@ func TestRender(t *testing.T) { { path: "1337", expected: "Gnoface #1337\n```" + ` - s - /|||||||\ + ....... + ||||||||| | | - | . * | -o| ~ ~ |. + | . _ | +D| x X |O | | - | O | + | ~ | | | - | __/ | + | ~~~ | | | - \_______/ + \~~~~~~~/ ` + "```\n", }, { path: "123456789", expected: "Gnoface #123456789\n```" + ` - s - /~~~~~~~\ + ....... + ////////\ | | - | ~ . | -<| ~ ~ |< + | ~ * | +|| x X |o | | | V | | | - | \_/ | + | . | | | \-------/ ` + "```\n", diff --git a/examples/gno.land/r/x/manfred_outfmt/gno.mod b/examples/gno.land/r/x/manfred_outfmt/gno.mod index 9804aecc7f1..7044f0f72b3 100644 --- a/examples/gno.land/r/x/manfred_outfmt/gno.mod +++ b/examples/gno.land/r/x/manfred_outfmt/gno.mod @@ -2,7 +2,4 @@ module gno.land/r/x/manfred_outfmt -require ( - gno.land/p/demo/rand v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) +require gno.land/p/demo/ufmt v0.0.0-latest diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno index 5468a65c06f..01981024189 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt.gno @@ -2,9 +2,9 @@ package outfmt import ( "encoding/json" + "math/rand" "strings" - "gno.land/p/demo/rand" "gno.land/p/demo/ufmt" ) @@ -27,22 +27,21 @@ func (res *Result) String() string { return output } -var rSeed int64 +var rSeed = rand.NewPCG(0, 0) func genResult() Result { - // init rand - r := rand.FromSeed(rSeed) - defer func() { rSeed = r.Seed() }() + r := rand.New(rSeed) + // init rand res := Result{ Text: "Hello Gnomes!", - Number: r.Intn(1000), + Number: r.IntN(1000), } - length := r.Intn(8) + 2 + length := r.IntN(8) + 2 res.Numbers = make([]int, length) for i := 0; i < length; i++ { - res.Numbers[i] = r.Intn(100) + res.Numbers[i] = r.IntN(100) } return res diff --git a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno index a60aeb384db..69c07bbbf16 100644 --- a/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno +++ b/examples/gno.land/r/x/manfred_outfmt/outfmt_test.gno @@ -15,7 +15,7 @@ func TestRender(t *testing.T) { * [?fmt=jsonp](/r/x/manfred_outfmt:?fmt=jsonp) ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -23,11 +23,11 @@ func TestRender(t *testing.T) { { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 957 -Numbers: 3 54 32 88 +Number: 222 +Numbers: 34 44 39 7 72 48 74 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -35,11 +35,11 @@ Numbers: 3 54 32 88 { got := outfmt.Render("?fmt=stringer") expected := `Text: Hello Gnomes! -Number: 141 -Numbers: 98 27 +Number: 898 +Numbers: 24 25 2 ` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } @@ -47,18 +47,18 @@ Numbers: 98 27 { got := outfmt.Render("?fmt=json") - expected := `{"Number":801,"Text":"Hello Gnomes!","Numbers":[5,78,51,78,91,41]}` + expected := `{"Number":746,"Text":"Hello Gnomes!","Numbers":[57,82,16,14,28,32]}` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } // jsonp { got := outfmt.Render("?fmt=jsonp") - expected := `callback({"Number":63,"Text":"Hello Gnomes!","Numbers":[2,66,50,73,81]})` + expected := `callback({"Number":795,"Text":"Hello Gnomes!","Numbers":[29,51,88,61,93,21,2,66,79]})` if got != expected { - t.Fatalf("expected %q, got %q.", expected, got) + t.Errorf("expected %q, got %q.", expected, got) } } } diff --git a/gnovm/stdlibs/bytes/buffer_test.gno b/gnovm/stdlibs/bytes/buffer_test.gno index a8837494224..601901955cd 100644 --- a/gnovm/stdlibs/bytes/buffer_test.gno +++ b/gnovm/stdlibs/bytes/buffer_test.gno @@ -216,14 +216,14 @@ func TestMixedReadsAndWrites(t *testing.T) { var buf bytes.Buffer s := "" for i := 0; i < 50; i++ { - wlen := rand.Intn(len(testString)) + wlen := rand.IntN(len(testString)) if i%2 == 0 { s = fillString(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testString[0:wlen]) } else { s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen]) } - rlen := rand.Intn(len(testString)) + rlen := rand.IntN(len(testString)) fub := make([]byte, rlen) n, _ := buf.Read(fub) s = s[n:] diff --git a/gnovm/stdlibs/bytes/bytes_test.gno b/gnovm/stdlibs/bytes/bytes_test.gno index c7762f2f67b..927f89c5559 100644 --- a/gnovm/stdlibs/bytes/bytes_test.gno +++ b/gnovm/stdlibs/bytes/bytes_test.gno @@ -1707,7 +1707,7 @@ var makeFieldsInput = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, ~10% 2-byte UTF-8, rest ASCII non-space. for i := range x { - switch rand.Intn(10) { + switch rand.IntN(10) { case 0: x[i] = ' ' case 1: @@ -1729,7 +1729,7 @@ var makeFieldsInputASCII = func() []byte { x := make([]byte, 1<<20) // Input is ~10% space, rest ASCII non-space. for i := range x { - if rand.Intn(10) == 0 { + if rand.IntN(10) == 0 { x[i] = ' ' } else { x[i] = 'x' @@ -1827,7 +1827,7 @@ func makeBenchInputHard() []byte { } x := make([]byte, 0, 1<<20) for { - i := rand.Intn(len(tokens)) + i := rand.IntN(len(tokens)) if len(x)+len(tokens[i]) >= 1<<20 { break } diff --git a/gnovm/stdlibs/math/rand/auto_test.gno b/gnovm/stdlibs/math/rand/auto_test.gno new file mode 100644 index 00000000000..5945039ae18 --- /dev/null +++ b/gnovm/stdlibs/math/rand/auto_test.gno @@ -0,0 +1,39 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "testing" +) + +// This test is first, in its own file with an alphabetically early name, +// to try to make sure that it runs early. It has the best chance of +// detecting deterministic seeding if it's the first test that runs. + +func TestAuto(t *testing.T) { + // Pull out 10 int64s from the global source + // and then check that they don't appear in that + // order in the deterministic seeded result. + var out []int64 + for i := 0; i < 10; i++ { + out = append(out, Int64()) + } + + // Look for out in seeded output. + // Strictly speaking, we should look for them in order, + // but this is good enough and not significantly more + // likely to have a false positive. + r := New(NewPCG(1, 0)) + found := 0 + for i := 0; i < 1000; i++ { + x := r.Int64() + if x == out[found] { + found++ + if found == len(out) { + t.Fatalf("found unseeded output in Seed(1) output") + } + } + } +} diff --git a/gnovm/stdlibs/math/rand/pcg.gno b/gnovm/stdlibs/math/rand/pcg.gno new file mode 100644 index 00000000000..77708d799e2 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg.gno @@ -0,0 +1,121 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "errors" + "math/bits" +) + +// https://numpy.org/devdocs/reference/random/upgrading-pcg64.html +// https://github.com/imneme/pcg-cpp/commit/871d0494ee9c9a7b7c43f753e3d8ca47c26f8005 + +// A PCG is a PCG generator with 128 bits of internal state. +// A zero PCG is equivalent to NewPCG(0, 0). +type PCG struct { + hi uint64 + lo uint64 +} + +// NewPCG returns a new PCG seeded with the given values. +func NewPCG(seed1, seed2 uint64) *PCG { + return &PCG{seed1, seed2} +} + +// Seed resets the PCG to behave the same way as NewPCG(seed1, seed2). +func (p *PCG) Seed(seed1, seed2 uint64) { + p.hi = seed1 + p.lo = seed2 +} + +// binary.bigEndian.Uint64, copied to avoid dependency +func beUint64(b []byte) uint64 { + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + 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 +} + +// binary.bigEndian.PutUint64, copied to avoid dependency +func bePutUint64(b []byte, v uint64) { + _ = b[7] // early bounds check to guarantee safety of writes below + 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) +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (p *PCG) MarshalBinary() ([]byte, error) { + b := make([]byte, 20) + copy(b, "pcg:") + bePutUint64(b[4:], p.hi) + bePutUint64(b[4+8:], p.lo) + return b, nil +} + +var errUnmarshalPCG = errors.New("invalid PCG encoding") + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (p *PCG) UnmarshalBinary(data []byte) error { + if len(data) != 20 || string(data[:4]) != "pcg:" { + return errUnmarshalPCG + } + p.hi = beUint64(data[4:]) + p.lo = beUint64(data[4+8:]) + return nil +} + +func (p *PCG) next() (hi, lo uint64) { + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L161 + // + // Numpy's PCG multiplies by the 64-bit value cheapMul + // instead of the 128-bit value used here and in the official PCG code. + // This does not seem worthwhile, at least for Go: not having any high + // bits in the multiplier reduces the effect of low bits on the highest bits, + // and it only saves 1 multiply out of 3. + // (On 32-bit systems, it saves 1 out of 6, since Mul64 is doing 4.) + const ( + mulHi = 2549297995355413924 + mulLo = 4865540595714422341 + incHi = 6364136223846793005 + incLo = 1442695040888963407 + ) + + // state = state * mul + inc + hi, lo = bits.Mul64(p.lo, mulLo) + hi += p.hi*mulLo + p.lo*mulHi + lo, c := bits.Add64(lo, incLo, 0) + hi, _ = bits.Add64(hi, incHi, c) + p.lo = lo + p.hi = hi + return hi, lo +} + +// Uint64 return a uniformly-distributed random uint64 value. +func (p *PCG) Uint64() uint64 { + hi, lo := p.next() + + // XSL-RR would be + // hi, lo := p.next() + // return bits.RotateLeft64(lo^hi, -int(hi>>58)) + // but Numpy uses DXSM and O'Neill suggests doing the same. + // See https://github.com/golang/go/issues/21835#issuecomment-739065688 + // and following comments. + + // DXSM "double xorshift multiply" + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L1015 + + // https://github.com/imneme/pcg-cpp/blob/428802d1a5/include/pcg_random.hpp#L176 + const cheapMul = 0xda942042e4dd58b5 + hi ^= hi >> 32 + hi *= cheapMul + hi ^= hi >> 48 + hi *= (lo | 1) + return hi +} diff --git a/gnovm/stdlibs/math/rand/pcg_test.gno b/gnovm/stdlibs/math/rand/pcg_test.gno new file mode 100644 index 00000000000..843506d7234 --- /dev/null +++ b/gnovm/stdlibs/math/rand/pcg_test.gno @@ -0,0 +1,78 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "testing" +) + +func BenchmarkPCG_DXSM(b *testing.B) { + var p PCG + var t uint64 + for n := b.N; n > 0; n-- { + t += p.Uint64() + } + Sink = t +} + +func TestPCGMarshal(t *testing.T) { + var p PCG + const ( + seed1 = 0x123456789abcdef0 + seed2 = 0xfedcba9876543210 + want = "pcg:\x12\x34\x56\x78\x9a\xbc\xde\xf0\xfe\xdc\xba\x98\x76\x54\x32\x10" + ) + p.Seed(seed1, seed2) + data, err := p.MarshalBinary() + if string(data) != want || err != nil { + t.Errorf("MarshalBinary() = %q, %v, want %q, nil", data, err, want) + } + + q := PCG{} + if err := q.UnmarshalBinary([]byte(want)); err != nil { + t.Fatalf("UnmarshalBinary(): %v", err) + } + if q != p { + t.Fatalf("after round trip, q = %#x, but p = %#x", q, p) + } + + qu := q.Uint64() + pu := p.Uint64() + if qu != pu { + t.Errorf("after round trip, q.Uint64() = %#x, but p.Uint64() = %#x", qu, pu) + } +} + +func TestPCG(t *testing.T) { + p := NewPCG(1, 2) + want := []uint64{ + 0xc4f5a58656eef510, + 0x9dcec3ad077dec6c, + 0xc8d04605312f8088, + 0xcbedc0dcb63ac19a, + 0x3bf98798cae97950, + 0xa8c6d7f8d485abc, + 0x7ffa3780429cd279, + 0x730ad2626b1c2f8e, + 0x21ff2330f4a0ad99, + 0x2f0901a1947094b0, + 0xa9735a3cfbe36cef, + 0x71ddb0a01a12c84a, + 0xf0e53e77a78453bb, + 0x1f173e9663be1e9d, + 0x657651da3ac4115e, + 0xc8987376b65a157b, + 0xbb17008f5fca28e7, + 0x8232bd645f29ed22, + 0x12be8f07ad14c539, + 0x54908a48e8e4736e, + } + + for i, x := range want { + if u := p.Uint64(); u != x { + t.Errorf("PCG #%d = %#x, want %#x", i, u, x) + } + } +} diff --git a/gnovm/stdlibs/math/rand/rand.gno b/gnovm/stdlibs/math/rand/rand.gno new file mode 100644 index 00000000000..250ea7c5a05 --- /dev/null +++ b/gnovm/stdlibs/math/rand/rand.gno @@ -0,0 +1,340 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rand implements pseudo-random number generators suitable for tasks +// such as simulation, but it should not be used for security-sensitive work. +// +// IMPORTANT NOTE: This does not equate to Go's math/rand; instead, it uses +// the implementation in math/rand/v2, which improves on its algorithm and +// usability. +// +// Random numbers are generated by a [Source], usually wrapped in a [Rand]. +// Both types should be used by a single goroutine at a time: sharing among +// multiple goroutines requires some kind of synchronization. +// +// Top-level functions, such as [Float64] and [Int], +// are safe for concurrent use by multiple goroutines. +// +// This package's outputs might be easily predictable regardless of how it's +// seeded. +package rand + +import ( + "math/bits" +) + +// A Source is a source of uniformly-distributed +// pseudo-random uint64 values in the range [0, 1<<64). +// +// A Source is not safe for concurrent use by multiple goroutines. +type Source interface { + Uint64() uint64 +} + +// A Rand is a source of random numbers. +type Rand struct { + src Source +} + +// New returns a new Rand that uses random values from src +// to generate other random values. +func New(src Source) *Rand { + return &Rand{src: src} +} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64. +func (r *Rand) Int64() int64 { return int64(r.src.Uint64() &^ (1 << 63)) } + +// Uint32 returns a pseudo-random 32-bit value as a uint32. +func (r *Rand) Uint32() uint32 { return uint32(r.src.Uint64() >> 32) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64. +func (r *Rand) Uint64() uint64 { return r.src.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32. +func (r *Rand) Int32() int32 { return int32(r.src.Uint64() >> 33) } + +// Int returns a non-negative pseudo-random int. +func (r *Rand) Int() int { return int(uint(r.src.Uint64()) << 1 >> 1) } + +// Int64N returns, as an int64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int64N(n int64) int64 { + if n <= 0 { + panic("invalid argument to Int64N") + } + return int64(r.uint64n(uint64(n))) +} + +// Uint64N returns, as a uint64, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint64N(n uint64) uint64 { + if n == 0 { + panic("invalid argument to Uint64N") + } + return r.uint64n(n) +} + +// uint64n is the no-bounds-checks version of Uint64N. +func (r *Rand) uint64n(n uint64) uint64 { + if is32bit && uint64(uint32(n)) == n { + return uint64(r.uint32n(uint32(n))) + } + if n&(n-1) == 0 { // n is power of two, can mask + return r.Uint64() & (n - 1) + } + + // Suppose we have a uint64 x uniform in the range [0,2⁶⁴) + // and want to reduce it to the range [0,n) preserving exact uniformity. + // We can simulate a scaling arbitrary precision x * (n/2⁶⁴) by + // the high bits of a double-width multiply of x*n, meaning (x*n)/2⁶⁴. + // Since there are 2⁶⁴ possible inputs x and only n possible outputs, + // the output is necessarily biased if n does not divide 2⁶⁴. + // In general (x*n)/2⁶⁴ = k for x*n in [k*2⁶⁴,(k+1)*2⁶⁴). + // There are either floor(2⁶⁴/n) or ceil(2⁶⁴/n) possible products + // in that range, depending on k. + // But suppose we reject the sample and try again when + // x*n is in [k*2⁶⁴, k*2⁶⁴+(2⁶⁴%n)), meaning rejecting fewer than n possible + // outcomes out of the 2⁶⁴. + // Now there are exactly floor(2⁶⁴/n) possible ways to produce + // each output value k, so we've restored uniformity. + // To get valid uint64 math, 2⁶⁴ % n = (2⁶⁴ - n) % n = -n % n, + // so the direct implementation of this algorithm would be: + // + // hi, lo := bits.Mul64(r.Uint64(), n) + // thresh := -n % n + // for lo < thresh { + // hi, lo = bits.Mul64(r.Uint64(), n) + // } + // + // That still leaves an expensive 64-bit division that we would rather avoid. + // We know that thresh < n, and n is usually much less than 2⁶⁴, so we can + // avoid the last four lines unless lo < n. + // + // See also: + // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction + // https://lemire.me/blog/2016/06/30/fast-random-shuffling + hi, lo := bits.Mul64(r.Uint64(), n) + if lo < n { + thresh := -n % n + for lo < thresh { + hi, lo = bits.Mul64(r.Uint64(), n) + } + } + return hi +} + +// uint32n is an identical computation to uint64n +// but optimized for 32-bit systems. +func (r *Rand) uint32n(n uint32) uint32 { + if n&(n-1) == 0 { // n is power of two, can mask + return uint32(r.Uint64()) & (n - 1) + } + // On 64-bit systems we still use the uint64 code below because + // the probability of a random uint64 lo being < a uint32 n is near zero, + // meaning the unbiasing loop almost never runs. + // On 32-bit systems, here we need to implement that same logic in 32-bit math, + // both to preserve the exact output sequence observed on 64-bit machines + // and to preserve the optimization that the unbiasing loop almost never runs. + // + // We want to compute + // hi, lo := bits.Mul64(r.Uint64(), n) + // In terms of 32-bit halves, this is: + // x1:x0 := r.Uint64() + // 0:hi, lo1:lo0 := bits.Mul64(x1:x0, 0:n) + // Writing out the multiplication in terms of bits.Mul32 allows + // using direct hardware instructions and avoiding + // the computations involving these zeros. + x := r.Uint64() + lo1a, lo0 := bits.Mul32(uint32(x), n) + hi, lo1b := bits.Mul32(uint32(x>>32), n) + lo1, c := bits.Add32(lo1a, lo1b, 0) + hi += c + if lo1 == 0 && lo0 < uint32(n) { + n64 := uint64(n) + thresh := uint32(-n64 % n64) + for lo1 == 0 && lo0 < thresh { + x := r.Uint64() + lo1a, lo0 = bits.Mul32(uint32(x), n) + hi, lo1b = bits.Mul32(uint32(x>>32), n) + lo1, c = bits.Add32(lo1a, lo1b, 0) + hi += c + } + } + return hi +} + +// Int32N returns, as an int32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) Int32N(n int32) int32 { + if n <= 0 { + panic("invalid argument to Int32N") + } + return int32(r.uint64n(uint64(n))) +} + +// Uint32N returns, as a uint32, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) Uint32N(n uint32) uint32 { + if n == 0 { + panic("invalid argument to Uint32N") + } + return uint32(r.uint64n(uint64(n))) +} + +const is32bit = ^uint(0)>>32 == 0 + +// IntN returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n <= 0. +func (r *Rand) IntN(n int) int { + if n <= 0 { + panic("invalid argument to IntN") + } + return int(r.uint64n(uint64(n))) +} + +// UintN returns, as a uint, a non-negative pseudo-random number in the half-open interval [0,n). +// It panics if n == 0. +func (r *Rand) UintN(n uint) uint { + if n == 0 { + panic("invalid argument to UintN") + } + return uint(r.uint64n(uint64(n))) +} + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float64() float64 { + // There are exactly 1<<53 float64s in [0,1). Use Intn(1<<53) / (1<<53). + return float64(r.Uint64()<<11>>11) / (1 << 53) +} + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0). +func (r *Rand) Float32() float32 { + // There are exactly 1<<24 float32s in [0,1). Use Intn(1<<24) / (1<<24). + return float32(r.Uint32()<<8>>8) / (1 << 24) +} + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n). +func (r *Rand) Perm(n int) []int { + p := make([]int, n) + for i := range p { + p[i] = i + } + r.Shuffle(len(p), func(i, j int) { p[i], p[j] = p[j], p[i] }) + return p +} + +// Shuffle pseudo-randomizes the order of elements. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func (r *Rand) Shuffle(n int, swap func(i, j int)) { + if n < 0 { + panic("invalid argument to Shuffle") + } + + // Fisher-Yates shuffle: https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle + // Shuffle really ought not be called with n that doesn't fit in 32 bits. + // Not only will it take a very long time, but with 2³¹! possible permutations, + // there's no way that any PRNG can have a big enough internal state to + // generate even a minuscule percentage of the possible permutations. + // Nevertheless, the right API signature accepts an int n, so handle it as best we can. + for i := n - 1; i > 0; i-- { + j := int(r.uint64n(uint64(i + 1))) + swap(i, j) + } +} + +/* + * Top-level convenience functions + */ + +// globalRand is the source of random numbers for the top-level +// convenience functions. +var globalRand = &Rand{src: &PCG{}} + +// Int64 returns a non-negative pseudo-random 63-bit integer as an int64 +// from the default Source. +func Int64() int64 { return globalRand.Int64() } + +// Uint32 returns a pseudo-random 32-bit value as a uint32 +// from the default Source. +func Uint32() uint32 { return globalRand.Uint32() } + +// Uint64N returns, as a uint64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint64N(n uint64) uint64 { return globalRand.Uint64N(n) } + +// Uint32N returns, as a uint32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Uint32N(n uint32) uint32 { return globalRand.Uint32N(n) } + +// Uint64 returns a pseudo-random 64-bit value as a uint64 +// from the default Source. +func Uint64() uint64 { return globalRand.Uint64() } + +// Int32 returns a non-negative pseudo-random 31-bit integer as an int32 +// from the default Source. +func Int32() int32 { return globalRand.Int32() } + +// Int returns a non-negative pseudo-random int from the default Source. +func Int() int { return globalRand.Int() } + +// Int64N returns, as an int64, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int64N(n int64) int64 { return globalRand.Int64N(n) } + +// Int32N returns, as an int32, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func Int32N(n int32) int32 { return globalRand.Int32N(n) } + +// IntN returns, as an int, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func IntN(n int) int { return globalRand.IntN(n) } + +// UintN returns, as a uint, a pseudo-random number in the half-open interval [0,n) +// from the default Source. +// It panics if n <= 0. +func UintN(n uint) uint { return globalRand.UintN(n) } + +// Float64 returns, as a float64, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float64() float64 { return globalRand.Float64() } + +// Float32 returns, as a float32, a pseudo-random number in the half-open interval [0.0,1.0) +// from the default Source. +func Float32() float32 { return globalRand.Float32() } + +// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers +// in the half-open interval [0,n) from the default Source. +func Perm(n int) []int { return globalRand.Perm(n) } + +// Shuffle pseudo-randomizes the order of elements using the default Source. +// n is the number of elements. Shuffle panics if n < 0. +// swap swaps the elements with indexes i and j. +func Shuffle(n int, swap func(i, j int)) { globalRand.Shuffle(n, swap) } + +// NormFloat64 returns a normally distributed float64 in the range +// [-math.MaxFloat64, +math.MaxFloat64] with +// standard normal distribution (mean = 0, stddev = 1) +// from the default Source. +// To produce a different normal distribution, callers can +// adjust the output using: +// +// sample = NormFloat64() * desiredStdDev + desiredMean +func NormFloat64() float64 { return globalRand.NormFloat64() } + +// ExpFloat64 returns an exponentially distributed float64 in the range +// (0, +math.MaxFloat64] with an exponential distribution whose rate parameter +// (lambda) is 1 and whose mean is 1/lambda (1) from the default Source. +// To produce a distribution with a different rate parameter, +// callers can adjust the output using: +// +// sample = ExpFloat64() / desiredRateParameter +func ExpFloat64() float64 { return globalRand.ExpFloat64() } diff --git a/gnovm/stdlibs/math/rand/zipf.gno b/gnovm/stdlibs/math/rand/zipf.gno new file mode 100644 index 00000000000..f04c814eb75 --- /dev/null +++ b/gnovm/stdlibs/math/rand/zipf.gno @@ -0,0 +1,77 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// W.Hormann, G.Derflinger: +// "Rejection-Inversion to Generate Variates +// from Monotone Discrete Distributions" +// http://eeyore.wu-wien.ac.at/papers/96-04-04.wh-der.ps.gz + +package rand + +import "math" + +// A Zipf generates Zipf distributed variates. +type Zipf struct { + r *Rand + imax float64 + v float64 + q float64 + s float64 + oneminusQ float64 + oneminusQinv float64 + hxm float64 + hx0minusHxm float64 +} + +func (z *Zipf) h(x float64) float64 { + return math.Exp(z.oneminusQ*math.Log(z.v+x)) * z.oneminusQinv +} + +func (z *Zipf) hinv(x float64) float64 { + return math.Exp(z.oneminusQinv*math.Log(z.oneminusQ*x)) - z.v +} + +// NewZipf returns a Zipf variate generator. +// The generator generates values k ∈ [0, imax] +// such that P(k) is proportional to (v + k) ** (-s). +// Requirements: s > 1 and v >= 1. +func NewZipf(r *Rand, s float64, v float64, imax uint64) *Zipf { + z := new(Zipf) + if s <= 1.0 || v < 1 { + return nil + } + z.r = r + z.imax = float64(imax) + z.v = v + z.q = s + z.oneminusQ = 1.0 - z.q + z.oneminusQinv = 1.0 / z.oneminusQ + z.hxm = z.h(z.imax + 0.5) + z.hx0minusHxm = z.h(0.5) - math.Exp(math.Log(z.v)*(-z.q)) - z.hxm + z.s = 1 - z.hinv(z.h(1.5)-math.Exp(-z.q*math.Log(z.v+1.0))) + return z +} + +// Uint64 returns a value drawn from the Zipf distribution described +// by the Zipf object. +func (z *Zipf) Uint64() uint64 { + if z == nil { + panic("rand: nil Zipf") + } + k := 0.0 + + for { + r := z.r.Float64() // r on [0,1] + ur := z.hxm + r*z.hx0minusHxm + x := z.hinv(ur) + k = math.Floor(x + 0.5) + if k-x <= z.s { + break + } + if ur >= z.h(k+0.5)-math.Exp(-math.Log(k+z.v)*z.q) { + break + } + } + return uint64(k) +} diff --git a/gnovm/stdlibs/sort/sort_test.gno b/gnovm/stdlibs/sort/sort_test.gno index 8416f0b77e1..dac6ab11f67 100644 --- a/gnovm/stdlibs/sort/sort_test.gno +++ b/gnovm/stdlibs/sort/sort_test.gno @@ -98,7 +98,7 @@ func TestSortLarge_Random(t *testing.T) { } data := make([]int, n) for i := 0; i < len(data); i++ { - data[i] = rand.Intn(100) + data[i] = rand.IntN(100) } if sort.IntsAreSorted(data) { t.Fatalf("terrible rand.rand") @@ -160,7 +160,7 @@ func TestNonDeterministicComparison(t *testing.T) { }() td := &nonDeterministicTestingData{ - r: rand.New(rand.NewSource(0)), + r: rand.New(rand.NewPCG(0, 0)), } for i := 0; i < 10; i++ { @@ -377,13 +377,13 @@ func testBentleyMcIlroy(t *testing.T, sortFn func(sort.Interface), maxswap func( case _Sawtooth: data[i] = i % m case _Rand: - data[i] = rand.Intn(m) + data[i] = rand.IntN(m) case _Stagger: data[i] = (i*m + i) % n case _Plateau: data[i] = min(i, m) case _Shuffle: - if rand.Intn(m) != 0 { + if rand.IntN(m) != 0 { j += 2 data[i] = j } else { @@ -587,7 +587,7 @@ func TestStability(t *testing.T) { // random distribution for i := 0; i < len(data); i++ { - data[i].a = rand.Intn(m) + data[i].a = rand.IntN(m) } if sort.IsSorted(data) { t.Fatalf("terrible rand.rand") @@ -643,7 +643,7 @@ func countOps(t *testing.T, algo func(sort.Interface), name string) { maxswap: 1<<31 - 1, } for i := 0; i < n; i++ { - td.data[i] = rand.Intn(n / 5) + td.data[i] = rand.IntN(n / 5) } algo(&td) t.Logf("%s %8d elements: %11d Swap, %10d Less", name, n, td.nswap, td.ncmp) diff --git a/gnovm/tests/files/extern/p1/s2.gno b/gnovm/tests/files/extern/p1/s2.gno index cfed6498051..01aa3f47da1 100644 --- a/gnovm/tests/files/extern/p1/s2.gno +++ b/gnovm/tests/files/extern/p1/s2.gno @@ -3,5 +3,3 @@ package p1 import "math/rand" var Uint32 = rand.Uint32 - -func init() { rand.Seed(1) } diff --git a/gnovm/tests/files/import4.gno b/gnovm/tests/files/import4.gno index dc52adafc31..91364f45389 100644 --- a/gnovm/tests/files/import4.gno +++ b/gnovm/tests/files/import4.gno @@ -5,4 +5,4 @@ import "github.com/gnolang/gno/_test/p1" func main() { println("num:", p1.Uint32()) } // Output: -// num: 2596996162 +// num: 956301160 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 3dbd292ea68..86c81be9a18 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -23,7 +23,7 @@ import ( "log" "math" "math/big" - "math/rand" + "math/rand/v2" "net" "net/url" "os" @@ -114,7 +114,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || pkgPath == "math/big" || - pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || mode == ImportModeNativePreferred { switch pkgPath { @@ -271,12 +270,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri case "math/rand": // XXX only expose for tests. pkg := gno.NewPackageNode("rand", pkgPath, nil) - pkg.DefineGoNativeValue("Intn", rand.Intn) - pkg.DefineGoNativeValue("Uint32", rand.Uint32) - pkg.DefineGoNativeValue("Seed", rand.Seed) - pkg.DefineGoNativeValue("New", rand.New) - pkg.DefineGoNativeValue("NewSource", rand.NewSource) - pkg.DefineGoNativeType(reflect.TypeOf(rand.Rand{})) + // make native rand same as gno rand. + rnd := rand.New(rand.NewPCG(0, 0)) //nolint:gosec + pkg.DefineGoNativeValue("IntN", rnd.IntN) + pkg.DefineGoNativeValue("Uint32", rnd.Uint32) return pkg, pkg.NewPackage() case "crypto/rand": pkg := gno.NewPackageNode("rand", pkgPath, nil) diff --git a/go.mod b/go.mod index 30a7f4dd84c..d2b4a5f9825 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/autocounterd/go.mod b/misc/autocounterd/go.mod index 0236341ccc3..12297e3c6ca 100644 --- a/misc/autocounterd/go.mod +++ b/misc/autocounterd/go.mod @@ -1,6 +1,6 @@ module loop -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index 25d9a218cf7..e0cb385020e 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/gno/misc/devdeps -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/misc/docs-linter/go.mod b/misc/docs-linter/go.mod index b92418db3dd..be771c9a952 100644 --- a/misc/docs-linter/go.mod +++ b/misc/docs-linter/go.mod @@ -1,6 +1,6 @@ module linter -go 1.21.6 +go 1.22 toolchain go1.22.4 diff --git a/misc/loop/go.mod b/misc/loop/go.mod index 4bb42f103e4..91d0d6cdf34 100644 --- a/misc/loop/go.mod +++ b/misc/loop/go.mod @@ -1,6 +1,6 @@ module loop -go 1.21 +go 1.22 toolchain go1.22.4 diff --git a/tm2/pkg/libtm/README.md b/tm2/pkg/libtm/README.md index d55a4cad305..bf58440e85e 100644 --- a/tm2/pkg/libtm/README.md +++ b/tm2/pkg/libtm/README.md @@ -45,7 +45,7 @@ To get up and running with the `libtm` package, you can add it to your project u go get -u github.com/gnolang/libtm ``` -Currently, the minimum required go version is `go 1.21`. +Currently, the minimum required go version is `go 1.22`. ## Usage Examples diff --git a/tm2/pkg/libtm/go.mod b/tm2/pkg/libtm/go.mod index a8f80c76e06..e903dd664e6 100644 --- a/tm2/pkg/libtm/go.mod +++ b/tm2/pkg/libtm/go.mod @@ -1,6 +1,6 @@ module github.com/gnolang/libtm -go 1.21 +go 1.22 toolchain go1.22.4 From ecb5522bf7323f5cd5db7a941d55a3012020ed23 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 21:24:59 +0200 Subject: [PATCH 3/6] feat: add p/demo/rand as p/demo/entropy Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/entropy/entropy.gno | 85 +++++++++++ .../entropy_test.gno} | 29 ++-- examples/gno.land/p/demo/entropy/gno.mod | 1 + .../gno.land/p/demo/entropy/z_filetest.gno | 56 +++++++ examples/gno.land/p/demo/rand/gno.mod | 3 - examples/gno.land/p/demo/rand/rand.gno | 139 ------------------ .../gno.land/p/demo/rand/rand0_filetest.gno | 56 ------- examples/gno.land/r/demo/art/gnoface/gno.mod | 5 +- .../gno.land/r/demo/art/gnoface/gnoface.gno | 3 +- 9 files changed, 162 insertions(+), 215 deletions(-) create mode 100644 examples/gno.land/p/demo/entropy/entropy.gno rename examples/gno.land/p/demo/{rand/rand_test.gno => entropy/entropy_test.gno} (50%) create mode 100644 examples/gno.land/p/demo/entropy/gno.mod create mode 100644 examples/gno.land/p/demo/entropy/z_filetest.gno delete mode 100644 examples/gno.land/p/demo/rand/gno.mod delete mode 100644 examples/gno.land/p/demo/rand/rand.gno delete mode 100644 examples/gno.land/p/demo/rand/rand0_filetest.gno diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno new file mode 100644 index 00000000000..1dd827940d1 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -0,0 +1,85 @@ +// Entropy generates fully deterministic, cost-effective, and hard to guess +// numbers. +// +// It is designed both for for single-usage, like seeding math/rand or for being +// reused which increases the entropy and its cost effectiveness. +// +// Disclaimer: this package is unsafe and won't prevent others to guess values +// in advance. +// +// It uses the Bernstein's hash djb2 to be CPU-cycle efficient. +package entropy + +import ( + "std" + "time" +) + +type Instance struct { + value int64 +} + +func New() *Instance { + r := Instance{value: 5381} + r.addEntropy() + return &r +} + +func FromSeed(seed int64) *Instance { + r := Instance{value: seed} + r.addEntropy() + return &r +} + +func (i *Instance) Seed() int64 { + return i.value +} + +func (i *Instance) djb2String(input string) { + for _, c := range input { + i.djb2Int64(int64(c)) + } +} + +// super fast random algorithm. +// http://www.cse.yorku.ca/~oz/hash.html +func (i *Instance) djb2Int64(input int64) { + i.value = (i.value << 5) + i.value + input +} + +// AddEntropy uses various runtime variables to add entropy to the existing seed. +func (i *Instance) addEntropy() { + // FIXME: reapply the 5381 initial value? + + // inherit previous entropy + // nothing to do + + // handle callers + { + caller1 := std.GetCallerAt(1).String() + i.djb2String(caller1) + caller2 := std.GetCallerAt(2).String() + i.djb2String(caller2) + } + + // height + { + height := std.GetHeight() + i.djb2Int64(height) + } + + // time + { + secs := time.Now().Second() + i.djb2Int64(int64(secs)) + nsecs := time.Now().Nanosecond() + i.djb2Int64(int64(nsecs)) + } + + // FIXME: compute other hard-to-guess but deterministic variables, like real gas? +} + +func (i *Instance) Value() int64 { + i.addEntropy() + return i.value +} diff --git a/examples/gno.land/p/demo/rand/rand_test.gno b/examples/gno.land/p/demo/entropy/entropy_test.gno similarity index 50% rename from examples/gno.land/p/demo/rand/rand_test.gno rename to examples/gno.land/p/demo/entropy/entropy_test.gno index 2651f0af089..013709bc4e9 100644 --- a/examples/gno.land/p/demo/rand/rand_test.gno +++ b/examples/gno.land/p/demo/entropy/entropy_test.gno @@ -1,49 +1,48 @@ -package rand +package entropy import ( "fmt" "std" + "strconv" "strings" "testing" - - "gno.land/p/demo/rand" ) func TestInstance(t *testing.T) { - instance := rand.New() + instance := New() if instance == nil { t.Errorf("instance should not be nil") } } -func TestIntn(t *testing.T) { - baseRand := rand.New() - baseResult := computeIntn(t, baseRand) +func TestInstanceValue(t *testing.T) { + baseEntropy := New() + baseResult := computeValue(t, baseEntropy) - sameHeightRand := rand.New() - sameHeightResult := computeIntn(t, sameHeightRand) + sameHeightEntropy := New() + sameHeightResult := computeValue(t, sameHeightEntropy) if baseResult != sameHeightResult { t.Errorf("should have the same result: new=%s, base=%s", sameHeightResult, baseResult) } std.TestSkipHeights(1) - differentHeightRand := rand.New() - differentHeightResult := computeIntn(t, differentHeightRand) + differentHeightEntropy := New() + differentHeightResult := computeValue(t, differentHeightEntropy) if baseResult == differentHeightResult { t.Errorf("should have different result: new=%s, base=%s", differentHeightResult, baseResult) } } -func computeIntn(t *testing.T, r *rand.Instance) string { +func computeValue(t *testing.T, r *Instance) string { t.Helper() - arr := []string{} + out := "" for i := 0; i < 10; i++ { - arr = append(arr, fmt.Sprintf("%d", r.Intn(1000))) + val := int(r.Value()) + out += strconv.Itoa(val) + " " } - out := strings.Join(arr, ",") return out } diff --git a/examples/gno.land/p/demo/entropy/gno.mod b/examples/gno.land/p/demo/entropy/gno.mod new file mode 100644 index 00000000000..9a6db8f5b61 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/entropy diff --git a/examples/gno.land/p/demo/entropy/z_filetest.gno b/examples/gno.land/p/demo/entropy/z_filetest.gno new file mode 100644 index 00000000000..b09f950b179 --- /dev/null +++ b/examples/gno.land/p/demo/entropy/z_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/entropy" +) + +func main() { + // initial + println("---") + r := entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + // should be the same + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + + std.TestSkipHeights(1) + println("---") + r = entropy.New() + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) + println(r.Value()) +} + +// Output: +// --- +// 2206404796634629535 +// 6369783909128776508 +// -273967328977352263 +// 3685013872379606294 +// -321443877504368301 +// --- +// 2206404796634629535 +// 6369783909128776508 +// -273967328977352263 +// 3685013872379606294 +// -321443877504368301 +// --- +// 2389594832880495019 +// 2483785090669486254 +// 902493904282098385 +// 5349860799650907156 +// 3037689170578704503 diff --git a/examples/gno.land/p/demo/rand/gno.mod b/examples/gno.land/p/demo/rand/gno.mod deleted file mode 100644 index 098af152648..00000000000 --- a/examples/gno.land/p/demo/rand/gno.mod +++ /dev/null @@ -1,3 +0,0 @@ -// Draft - -module gno.land/p/demo/rand diff --git a/examples/gno.land/p/demo/rand/rand.gno b/examples/gno.land/p/demo/rand/rand.gno deleted file mode 100644 index 2fa16d627be..00000000000 --- a/examples/gno.land/p/demo/rand/rand.gno +++ /dev/null @@ -1,139 +0,0 @@ -package rand - -// Disclaimer: this package is unsafe and won't prevent others to -// guess values in advance. -// -// the goal of this package is to implement a random library that -// is fully deterministic for validators while being hard to guess. -// -// We use the Bernstein's hash djb2 to be CPU-cycle efficient. - -import ( - "math/rand" - "std" - "time" -) - -type Instance struct { - seed int64 -} - -func New() *Instance { - r := Instance{seed: 5381} - r.addEntropy() - return &r -} - -func FromSeed(seed int64) *Instance { - r := Instance{seed: seed} - r.addEntropy() - return &r -} - -func (i *Instance) Seed() int64 { - return i.seed -} - -func (i *Instance) djb2String(input string) { - for _, c := range input { - i.djb2Int64(int64(c)) - } -} - -// super fast random algorithm. -// http://www.cse.yorku.ca/~oz/hash.html -func (i *Instance) djb2Int64(input int64) { - i.seed = (i.seed << 5) + i.seed + input -} - -// AddEntropy uses various runtime variables to add entropy to the existing seed. -func (i *Instance) addEntropy() { - // FIXME: reapply the 5381 initial value? - - // inherit previous entropy - // nothing to do - - // handle callers - { - caller1 := std.GetCallerAt(1).String() - i.djb2String(caller1) - caller2 := std.GetCallerAt(2).String() - i.djb2String(caller2) - } - - // height - { - height := std.GetHeight() - i.djb2Int64(height) - } - - // time - { - secs := time.Now().Second() - i.djb2Int64(int64(secs)) - nsecs := time.Now().Nanosecond() - i.djb2Int64(int64(nsecs)) - } - - // FIXME: compute other hard-to-guess but deterministic variables, like real gas? -} - -func (i *Instance) Float32() float32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float32() -} - -func (i *Instance) Float64() float64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Float64() -} - -func (i *Instance) Int() int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int() -} - -func (i *Instance) Intn(n int) int { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Intn(n) -} - -func (i *Instance) Int63() int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63() -} - -func (i *Instance) Int63n(n int64) int64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int63n(n) -} - -func (i *Instance) Int31() int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31() -} - -func (i *Instance) Int31n(n int32) int32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Int31n(n) -} - -func (i *Instance) Uint32() uint32 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint32() -} - -func (i *Instance) Uint64() uint64 { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Uint64() -} - -func (i *Instance) Read(p []byte) (n int, err error) { - i.addEntropy() - return rand.New(rand.NewSource(i.seed)).Read(p) -} - -func (i *Instance) Shuffle(n int, swap func(i, j int)) { - i.addEntropy() - rand.New(rand.NewSource(i.seed)).Shuffle(n, swap) -} diff --git a/examples/gno.land/p/demo/rand/rand0_filetest.gno b/examples/gno.land/p/demo/rand/rand0_filetest.gno deleted file mode 100644 index 446e04b696d..00000000000 --- a/examples/gno.land/p/demo/rand/rand0_filetest.gno +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "std" - - "gno.land/p/demo/rand" -) - -func main() { - // initial - println("---") - r := rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - // should be the same - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - - std.TestSkipHeights(1) - println("---") - r = rand.New() - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) - println(r.Intn(1000)) -} - -// Output: -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 777 -// 257 -// 74 -// 177 -// 802 -// --- -// 269 -// 233 -// 591 -// 936 -// 908 diff --git a/examples/gno.land/r/demo/art/gnoface/gno.mod b/examples/gno.land/r/demo/art/gnoface/gno.mod index f2d3ddebadc..d3c8feb3c84 100644 --- a/examples/gno.land/r/demo/art/gnoface/gno.mod +++ b/examples/gno.land/r/demo/art/gnoface/gno.mod @@ -1,3 +1,6 @@ module gno.land/r/demo/art/gnoface -require gno.land/p/demo/ufmt v0.0.0-latest +require ( + gno.land/p/demo/entropy v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index 9e85c5c7387..e8239412c4f 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -6,11 +6,12 @@ import ( "strconv" "strings" + "gno.land/p/demo/entropy" "gno.land/p/demo/ufmt" ) func Render(path string) string { - seed := uint64(std.GetHeight()) + seed := uint64(entropy.New().Value()) path = strings.TrimSpace(path) if path != "" { From 6d9527d7d0971f9ee81211875f9f5647f6df2c77 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Tue, 2 Jul 2024 22:46:53 -0700 Subject: [PATCH 4/6] chore: fixup Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/entropy/entropy_test.gno | 2 -- examples/gno.land/r/demo/art/gnoface/gnoface.gno | 1 - 2 files changed, 3 deletions(-) diff --git a/examples/gno.land/p/demo/entropy/entropy_test.gno b/examples/gno.land/p/demo/entropy/entropy_test.gno index 013709bc4e9..0deb3ab9aa2 100644 --- a/examples/gno.land/p/demo/entropy/entropy_test.gno +++ b/examples/gno.land/p/demo/entropy/entropy_test.gno @@ -1,10 +1,8 @@ package entropy import ( - "fmt" "std" "strconv" - "strings" "testing" ) diff --git a/examples/gno.land/r/demo/art/gnoface/gnoface.gno b/examples/gno.land/r/demo/art/gnoface/gnoface.gno index e8239412c4f..b4bc8e222e5 100644 --- a/examples/gno.land/r/demo/art/gnoface/gnoface.gno +++ b/examples/gno.land/r/demo/art/gnoface/gnoface.gno @@ -2,7 +2,6 @@ package gnoface import ( "math/rand" - "std" "strconv" "strings" From 1f867b6db7955b6cd06e55df97090941b998617d Mon Sep 17 00:00:00 2001 From: Manfred Touron <94029+moul@users.noreply.github.com> Date: Fri, 5 Jul 2024 07:07:35 +0200 Subject: [PATCH 5/6] Update examples/gno.land/p/demo/entropy/entropy.gno Co-authored-by: Marc Vertes --- examples/gno.land/p/demo/entropy/entropy.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno index 1dd827940d1..d28b6689241 100644 --- a/examples/gno.land/p/demo/entropy/entropy.gno +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -1,7 +1,7 @@ // Entropy generates fully deterministic, cost-effective, and hard to guess // numbers. // -// It is designed both for for single-usage, like seeding math/rand or for being +// It is designed both for single-usage, like seeding math/rand or for being // reused which increases the entropy and its cost effectiveness. // // Disclaimer: this package is unsafe and won't prevent others to guess values From 336b8a3e5e1b1c27f9d604c79eba8c507d908a32 Mon Sep 17 00:00:00 2001 From: moul <94029+moul@users.noreply.github.com> Date: Thu, 4 Jul 2024 22:14:37 -0700 Subject: [PATCH 6/6] chore: switch to uint32 Signed-off-by: moul <94029+moul@users.noreply.github.com> --- examples/gno.land/p/demo/entropy/entropy.gno | 22 ++++++++------ .../gno.land/p/demo/entropy/z_filetest.gno | 30 +++++++++---------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/examples/gno.land/p/demo/entropy/entropy.gno b/examples/gno.land/p/demo/entropy/entropy.gno index d28b6689241..5e35b8c7227 100644 --- a/examples/gno.land/p/demo/entropy/entropy.gno +++ b/examples/gno.land/p/demo/entropy/entropy.gno @@ -11,12 +11,13 @@ package entropy import ( + "math" "std" "time" ) type Instance struct { - value int64 + value uint32 } func New() *Instance { @@ -25,25 +26,25 @@ func New() *Instance { return &r } -func FromSeed(seed int64) *Instance { +func FromSeed(seed uint32) *Instance { r := Instance{value: seed} r.addEntropy() return &r } -func (i *Instance) Seed() int64 { +func (i *Instance) Seed() uint32 { return i.value } func (i *Instance) djb2String(input string) { for _, c := range input { - i.djb2Int64(int64(c)) + i.djb2Uint32(uint32(c)) } } // super fast random algorithm. // http://www.cse.yorku.ca/~oz/hash.html -func (i *Instance) djb2Int64(input int64) { +func (i *Instance) djb2Uint32(input uint32) { i.value = (i.value << 5) + i.value + input } @@ -65,21 +66,24 @@ func (i *Instance) addEntropy() { // height { height := std.GetHeight() - i.djb2Int64(height) + if height >= math.MaxUint32 { + height -= math.MaxUint32 + } + i.djb2Uint32(uint32(height)) } // time { secs := time.Now().Second() - i.djb2Int64(int64(secs)) + i.djb2Uint32(uint32(secs)) nsecs := time.Now().Nanosecond() - i.djb2Int64(int64(nsecs)) + i.djb2Uint32(uint32(nsecs)) } // FIXME: compute other hard-to-guess but deterministic variables, like real gas? } -func (i *Instance) Value() int64 { +func (i *Instance) Value() uint32 { i.addEntropy() return i.value } diff --git a/examples/gno.land/p/demo/entropy/z_filetest.gno b/examples/gno.land/p/demo/entropy/z_filetest.gno index b09f950b179..85ed1b10a3d 100644 --- a/examples/gno.land/p/demo/entropy/z_filetest.gno +++ b/examples/gno.land/p/demo/entropy/z_filetest.gno @@ -37,20 +37,20 @@ func main() { // Output: // --- -// 2206404796634629535 -// 6369783909128776508 -// -273967328977352263 -// 3685013872379606294 -// -321443877504368301 +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 // --- -// 2206404796634629535 -// 6369783909128776508 -// -273967328977352263 -// 3685013872379606294 -// -321443877504368301 +// 4129293727 +// 2141104956 +// 1950222777 +// 3348280598 +// 438354259 // --- -// 2389594832880495019 -// 2483785090669486254 -// 902493904282098385 -// 5349860799650907156 -// 3037689170578704503 +// 49506731 +// 1539580078 +// 2695928529 +// 1895482388 +// 3462727799