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.