From 688edf5deccd68b99a5f2c9ff416b95738938a6f Mon Sep 17 00:00:00 2001 From: Leon Hudak <33522493+leohhhn@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:51:32 +0200 Subject: [PATCH] feat(stdlibs): add `math/overflow` (#1698) ## Description This PR ports over the overflow stdlib to Gno. The addition of this library will enable adding Coin functionality already existing in tm2's [coin.go](https://github.com/gnolang/gno/blob/master/tm2/pkg/std/coin.go) the [Coin/Coins](https://github.com/gnolang/gno/blob/master/gnovm/stdlibs/std/coins.gno) in Gno. The tests pass fully, and the library was simply copy-pasted. As per the conversation below, this PR also deprecates/removes the `examples/` package for `overflow`, as it makes more sense to have `overflow` be a part of stdlibs. This will unblock #1696. cc @thehowl
Contributors' checklist... - [x] Added new tests, or not needed, or not feasible - [x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory - [x] Updated the official documentation or not needed - [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message was included in the description - [x] Added references to related issues and PRs - [x] Provided any useful hints for running manual tests - [ ] Added new benchmarks to [generated graphs](https://gnoland.github.io/benchmarks), if any. More info [here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
--------- Co-authored-by: Hariom Verma --- examples/gno.land/p/demo/groups/gno.mod | 2 +- examples/gno.land/p/demo/groups/vote_set.gno | 6 +- examples/gno.land/p/demo/maths/gno.mod | 1 - examples/gno.land/p/demo/rat/gno.mod | 1 + .../gno.land/p/demo/{maths => rat}/maths.gno | 2 +- .../gno.land/p/demo/{maths => rat}/rat.gno | 2 +- .../stdlibs/math/overflow}/overflow.gno | 19 +- gnovm/stdlibs/math/overflow/overflow_test.gno | 200 ++++++++++++++++++ gnovm/tests/files/maths_int16_long.gno | 100 --------- gnovm/tests/files/maths_int8.gno | 95 --------- gnovm/tests/imports.go | 10 + 11 files changed, 228 insertions(+), 210 deletions(-) delete mode 100644 examples/gno.land/p/demo/maths/gno.mod create mode 100644 examples/gno.land/p/demo/rat/gno.mod rename examples/gno.land/p/demo/{maths => rat}/maths.gno (96%) rename examples/gno.land/p/demo/{maths => rat}/rat.gno (98%) rename {examples/gno.land/p/demo/maths => gnovm/stdlibs/math/overflow}/overflow.gno (97%) create mode 100644 gnovm/stdlibs/math/overflow/overflow_test.gno delete mode 100644 gnovm/tests/files/maths_int16_long.gno delete mode 100644 gnovm/tests/files/maths_int8.gno diff --git a/examples/gno.land/p/demo/groups/gno.mod b/examples/gno.land/p/demo/groups/gno.mod index 0e9f7cf2a7c..f0749e3f411 100644 --- a/examples/gno.land/p/demo/groups/gno.mod +++ b/examples/gno.land/p/demo/groups/gno.mod @@ -1,6 +1,6 @@ module gno.land/p/demo/groups require ( - gno.land/p/demo/maths v0.0.0-latest + gno.land/p/demo/rat v0.0.0-latest gno.land/r/demo/boards v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/groups/vote_set.gno b/examples/gno.land/p/demo/groups/vote_set.gno index 22b29045d91..f66fb0fc8d8 100644 --- a/examples/gno.land/p/demo/groups/vote_set.gno +++ b/examples/gno.land/p/demo/groups/vote_set.gno @@ -4,7 +4,7 @@ import ( "std" "time" - "gno.land/p/demo/maths" + "gno.land/p/demo/rat" ) //---------------------------------------- @@ -71,8 +71,8 @@ func (vlist *VoteList) CountVotes(target string) int { // Committee type Committee struct { - Quorum maths.Rat - Threshold maths.Rat + Quorum rat.Rat + Threshold rat.Rat Addresses std.AddressSet } diff --git a/examples/gno.land/p/demo/maths/gno.mod b/examples/gno.land/p/demo/maths/gno.mod deleted file mode 100644 index d320b653702..00000000000 --- a/examples/gno.land/p/demo/maths/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/p/demo/maths diff --git a/examples/gno.land/p/demo/rat/gno.mod b/examples/gno.land/p/demo/rat/gno.mod new file mode 100644 index 00000000000..c20136fe9bb --- /dev/null +++ b/examples/gno.land/p/demo/rat/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/rat diff --git a/examples/gno.land/p/demo/maths/maths.gno b/examples/gno.land/p/demo/rat/maths.gno similarity index 96% rename from examples/gno.land/p/demo/maths/maths.gno rename to examples/gno.land/p/demo/rat/maths.gno index 0b3ce3c30e5..115e3cad5d8 100644 --- a/examples/gno.land/p/demo/maths/maths.gno +++ b/examples/gno.land/p/demo/rat/maths.gno @@ -1,4 +1,4 @@ -package maths +package rat const ( intSize = 32 << (^uint(0) >> 63) // 32 or 64 diff --git a/examples/gno.land/p/demo/maths/rat.gno b/examples/gno.land/p/demo/rat/rat.gno similarity index 98% rename from examples/gno.land/p/demo/maths/rat.gno rename to examples/gno.land/p/demo/rat/rat.gno index a7520fb016e..ea53343c490 100644 --- a/examples/gno.land/p/demo/maths/rat.gno +++ b/examples/gno.land/p/demo/rat/rat.gno @@ -1,4 +1,4 @@ -package maths +package rat //---------------------------------------- // Rat fractions diff --git a/examples/gno.land/p/demo/maths/overflow.gno b/gnovm/stdlibs/math/overflow/overflow.gno similarity index 97% rename from examples/gno.land/p/demo/maths/overflow.gno rename to gnovm/stdlibs/math/overflow/overflow.gno index f88f8014da2..9bdeff0720f 100644 --- a/examples/gno.land/p/demo/maths/overflow.gno +++ b/gnovm/stdlibs/math/overflow/overflow.gno @@ -2,8 +2,7 @@ // NOTE: there was a bug with the original Quotient* functions, and // testing method. These have been fixed here, and tests ported to // tests/files/maths_int*.go respectively. -// TODO: make PR upstream. -package maths +// Note: moved over from p/demo/maths. /* Package overflow offers overflow-checked integer arithmetic operations @@ -24,12 +23,16 @@ overflow.Add(math.MaxInt64,1) -> (0, false) Add, Sub, Mul, Div are for int. Add64, Add32, etc. are specifically sized. If anybody wishes an unsigned version, submit a pull request for code -and new tests. */ +and new tests. +*/ +package overflow + +import "math" //go:generate ./overflow_template.sh func _is64Bit() bool { - maxU32 := uint(MaxUint32) + maxU32 := uint(math.MaxUint32) return ((maxU32 << 1) >> 1) == maxU32 } @@ -220,7 +223,7 @@ func Div8p(a, b int8) int8 { func Quo8(a, b int8) (int8, int8, bool) { if b == 0 { return 0, 0, false - } else if b == -1 && a == MinInt8 { + } else if b == -1 && a == math.MinInt8 { return 0, 0, false } c := a / b @@ -310,7 +313,7 @@ func Div16p(a, b int16) int16 { func Quo16(a, b int16) (int16, int16, bool) { if b == 0 { return 0, 0, false - } else if b == -1 && a == MinInt16 { + } else if b == -1 && a == math.MinInt16 { return 0, 0, false } c := a / b @@ -400,7 +403,7 @@ func Div32p(a, b int32) int32 { func Quo32(a, b int32) (int32, int32, bool) { if b == 0 { return 0, 0, false - } else if b == -1 && a == MinInt32 { + } else if b == -1 && a == math.MinInt32 { return 0, 0, false } c := a / b @@ -490,7 +493,7 @@ func Div64p(a, b int64) int64 { func Quo64(a, b int64) (int64, int64, bool) { if b == 0 { return 0, 0, false - } else if b == -1 && a == MinInt64 { + } else if b == -1 && a == math.MinInt64 { return 0, 0, false } c := a / b diff --git a/gnovm/stdlibs/math/overflow/overflow_test.gno b/gnovm/stdlibs/math/overflow/overflow_test.gno new file mode 100644 index 00000000000..b7881aec480 --- /dev/null +++ b/gnovm/stdlibs/math/overflow/overflow_test.gno @@ -0,0 +1,200 @@ +package overflow + +import ( + "math" + "testing" +) + +// sample all possibilities of 8 bit numbers +// by checking against 64 bit numbers + +func TestAlgorithms(t *testing.T) { + errors := 0 + + for a64 := int64(math.MinInt8); a64 <= int64(math.MaxInt8); a64++ { + for b64 := int64(math.MinInt8); b64 <= int64(math.MaxInt8) && errors < 10; b64++ { + + a8 := int8(a64) + b8 := int8(b64) + + if int64(a8) != a64 || int64(b8) != b64 { + t.Fatal("LOGIC FAILURE IN TEST") + } + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v + %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v - %v = %v instead of %v\n", + a8, b8, result, r64) + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v * %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if !ok && int64(result) == r64 { + t.Fail() + errors++ + } + } + + // DIVISION + if b8 != 0 { + r64 := a64 / b64 + rem64 := a64 % b64 + + // now the verification + result, rem, ok := Quo8(a8, b8) + if ok && int64(result) != r64 { + t.Errorf("failed to fail on %v / %v = %v instead of %v\n", + a8, b8, result, r64) + errors++ + } + if ok && int64(rem) != rem64 { + t.Errorf("failed to fail on %v %% %v = %v instead of %v\n", + a8, b8, rem, rem64) + errors++ + } + } + } + } +} + +func TestQuotient(t *testing.T) { + q, r, ok := Quo(100, 3) + if r != 1 || q != 33 || !ok { + t.Errorf("expected 100/3 => 33, r=1") + } + if _, _, ok = Quo(1, 0); ok { + t.Error("unexpected lack of failure") + } +} + +func TestLong(t *testing.T) { + if testing.Short() { + t.Skip() + } + + ctr := int64(0) + + for a64 := int64(math.MinInt16); a64 <= int64(math.MaxInt16); a64++ { + for b64 := int64(math.MinInt16); b64 <= int64(math.MaxInt16); b64++ { + a16 := int16(a64) + b16 := int16(b64) + if int64(a16) != a64 || int64(b16) != b64 { + panic("LOGIC FAILURE IN TEST") + } + ctr++ + + // ADDITION + { + r64 := a64 + b64 + + // now the verification + result, ok := Add16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("add", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("add", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // SUBTRACTION + { + r64 := a64 - b64 + + // now the verification + result, ok := Sub16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("sub", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("sub", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // MULTIPLICATION + { + r64 := a64 * b64 + + // now the verification + result, ok := Mul16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("mul", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("mul", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + + // DIVISION + if b16 != 0 { + r64 := a64 / b64 + + // now the verification + result, _, ok := Quo16(a16, b16) + if int64(math.MinInt16) <= r64 && r64 <= int64(math.MaxInt16) { + if !ok || int64(result) != r64 { + println("quo", a16, b16, result, r64) + panic("incorrect result for non-overflow") + } + } else { + if ok { + println("quo", a16, b16, result, r64) + panic("incorrect ok result") + } + } + } + } + } + println("done", ctr) +} diff --git a/gnovm/tests/files/maths_int16_long.gno b/gnovm/tests/files/maths_int16_long.gno deleted file mode 100644 index efaf0ea9346..00000000000 --- a/gnovm/tests/files/maths_int16_long.gno +++ /dev/null @@ -1,100 +0,0 @@ -package main - -import ( - "gno.land/p/demo/maths" -) - -func main() { - ctr := int64(0) - - for a64 := int64(maths.MinInt16); a64 <= int64(maths.MaxInt16); a64++ { - for b64 := int64(maths.MinInt16); b64 <= int64(maths.MaxInt16); b64++ { - a16 := int16(a64) - b16 := int16(b64) - if int64(a16) != a64 || int64(b16) != b64 { - panic("LOGIC FAILURE IN TEST") - } - ctr++ - - // ADDITION - { - r64 := a64 + b64 - - // now the verification - result, ok := maths.Add16(a16, b16) - if int64(maths.MinInt16) <= r64 && r64 <= int64(maths.MaxInt16) { - if !ok || int64(result) != r64 { - println("add", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("add", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // SUBTRACTION - { - r64 := a64 - b64 - - // now the verification - result, ok := maths.Sub16(a16, b16) - if int64(maths.MinInt16) <= r64 && r64 <= int64(maths.MaxInt16) { - if !ok || int64(result) != r64 { - println("sub", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("sub", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // MULTIPLICATION - { - r64 := a64 * b64 - - // now the verification - result, ok := maths.Mul16(a16, b16) - if int64(maths.MinInt16) <= r64 && r64 <= int64(maths.MaxInt16) { - if !ok || int64(result) != r64 { - println("mul", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("mul", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - - // DIVISION - if b16 != 0 { - r64 := a64 / b64 - - // now the verification - result, _, ok := maths.Quo16(a16, b16) - if int64(maths.MinInt16) <= r64 && r64 <= int64(maths.MaxInt16) { - if !ok || int64(result) != r64 { - println("quo", a16, b16, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - println("quo", a16, b16, result, r64) - panic("incorrect ok result") - } - } - } - } - } - println("done", ctr) -} - -// Output: -// done XXXXXX diff --git a/gnovm/tests/files/maths_int8.gno b/gnovm/tests/files/maths_int8.gno deleted file mode 100644 index 40e7a2a62e0..00000000000 --- a/gnovm/tests/files/maths_int8.gno +++ /dev/null @@ -1,95 +0,0 @@ -package main - -import ( - "gno.land/p/demo/maths" -) - -func main() { - ctr := int64(0) - - for a64 := int64(maths.MinInt8); a64 <= int64(maths.MaxInt8); a64++ { - for b64 := int64(maths.MinInt8); b64 <= int64(maths.MaxInt8); b64++ { - a8 := int8(a64) - b8 := int8(b64) - if int64(a8) != a64 || int64(b8) != b64 { - panic("LOGIC FAILURE IN TEST") - } - ctr++ - - // ADDITION - { - r64 := a64 + b64 - - // now the verification - result, ok := maths.Add8(a8, b8) - if int64(maths.MinInt8) <= r64 && r64 <= int64(maths.MaxInt8) { - if !ok || int64(result) != r64 { - println("add", a8, b8, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - panic("incorrect ok result") - } - } - } - - // SUBTRACTION - { - r64 := a64 - b64 - - // now the verification - result, ok := maths.Sub8(a8, b8) - if int64(maths.MinInt8) <= r64 && r64 <= int64(maths.MaxInt8) { - if !ok || int64(result) != r64 { - println("sub", a8, b8, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - panic("incorrect ok result") - } - } - } - - // MULTIPLICATION - { - r64 := a64 * b64 - - // now the verification - result, ok := maths.Mul8(a8, b8) - if int64(maths.MinInt8) <= r64 && r64 <= int64(maths.MaxInt8) { - if !ok || int64(result) != r64 { - println("mul", a8, b8, result, r64) - panic("incorrect result for non-overflow") - } - } else { - if ok { - panic("incorrect ok result") - } - } - } - - // DIVISION - if b8 != 0 { - r64 := a64 / b64 - - // now the verification - result, _, ok := maths.Quo8(a8, b8) - if int64(maths.MinInt8) <= r64 && r64 <= int64(maths.MaxInt8) { - if !ok || int64(result) != r64 { - panic("incorrect result for non-overflow") - } - } else { - if ok { - panic("incorrect ok result") - } - } - } - } - } - println("done", ctr) -} - -// Output: -// done 65536 diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 9c48b4132cf..389c6717780 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -254,6 +254,16 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Pi", math.Pi) pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32) pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64) + pkg.DefineGoNativeValue("MaxUint32", math.MaxUint32) + pkg.DefineGoNativeValue("MaxUint64", uint64(math.MaxUint64)) + pkg.DefineGoNativeValue("MinInt8", math.MinInt8) + pkg.DefineGoNativeValue("MinInt16", math.MinInt16) + pkg.DefineGoNativeValue("MinInt32", math.MinInt32) + pkg.DefineGoNativeValue("MinInt64", math.MinInt64) + pkg.DefineGoNativeValue("MaxInt8", math.MaxInt8) + pkg.DefineGoNativeValue("MaxInt16", math.MaxInt16) + pkg.DefineGoNativeValue("MaxInt32", math.MaxInt32) + pkg.DefineGoNativeValue("MaxInt64", math.MaxInt64) return pkg, pkg.NewPackage() case "math/rand": // XXX only expose for tests.