Skip to content

Commit

Permalink
feat(p/int256): int256 (#1848)
Browse files Browse the repository at this point in the history
This pr implements `int256` package to handle huge numbers in gno.
To support this, it uses `uint256` package in its own struct
> there is pr to bring holiman's uint256 into gno-vm #1778 

origin(go) implementation: https://github.com/mempooler/int256
gnoswap(gno) implementation:
[https://github.com/gnoswap-labs/gnoswap/package/big/int256](https://github.com/gnoswap-labs/gnoswap/tree/51439a5b097e6c523cb8a77b1a792b7f7aacb863/packages/big/int256
)

### gno test metrics
```bash
gno test -v=true -print-runtime-metrics=true ./examples/gno.land/p/demo/int256

=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
---       runtime: cycle=30.5k imports=3 allocs=797.6k(0.16%)
=== RUN   TestAbsGt
--- PASS: TestAbsGt (0.00s)
---       runtime: cycle=108.7k imports=3 allocs=2.5M(0.50%)
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
---       runtime: cycle=155.2k imports=3 allocs=4.0M(0.79%)
=== RUN   TestSub
--- PASS: TestSub (0.00s)
---       runtime: cycle=259.1k imports=3 allocs=6.3M(1.26%)
=== RUN   TestMul
--- PASS: TestMul (0.00s)
---       runtime: cycle=278.4k imports=3 allocs=7.0M(1.39%)
=== RUN   TestDiv
--- PASS: TestDiv (0.00s)
---       runtime: cycle=307.6k imports=3 allocs=8.1M(1.62%)
=== RUN   TestRem
--- PASS: TestRem (0.00s)
---       runtime: cycle=342.8k imports=3 allocs=9.5M(1.90%)
=== RUN   TestEq
--- PASS: TestEq (0.00s)
---       runtime: cycle=422.1k imports=3 allocs=11.3M(2.25%)
=== RUN   TestCmp
--- PASS: TestCmp (0.00s)
---       runtime: cycle=466.7k imports=3 allocs=12.4M(2.47%)
=== RUN   TestIsZero
--- PASS: TestIsZero (0.00s)
---       runtime: cycle=471.3k imports=3 allocs=12.6M(2.51%)
=== RUN   TestIsNeg
--- PASS: TestIsNeg (0.00s)
---       runtime: cycle=476.6k imports=3 allocs=12.8M(2.56%)
=== RUN   TestLt
--- PASS: TestLt (0.00s)
---       runtime: cycle=522.7k imports=3 allocs=14.0M(2.79%)
=== RUN   TestClone
--- PASS: TestClone (0.00s)
---       runtime: cycle=565.1k imports=3 allocs=14.9M(2.98%)
=== RUN   TestSetInt64
--- PASS: TestSetInt64 (0.00s)
---       runtime: cycle=567.2k imports=3 allocs=15.0M(3.01%)
=== RUN   TestSetUint64
--- PASS: TestSetUint64 (0.00s)
---       runtime: cycle=568.3k imports=3 allocs=15.1M(3.02%)
=== RUN   TestUint64
--- PASS: TestUint64 (0.00s)
---       runtime: cycle=605.7k imports=3 allocs=16.0M(3.21%)
=== RUN   TestInt64
--- PASS: TestInt64 (0.00s)
---       runtime: cycle=624.5k imports=3 allocs=16.5M(3.31%)
=== RUN   TestNeg
--- PASS: TestNeg (0.00s)
---       runtime: cycle=636.2k imports=3 allocs=16.9M(3.38%)
=== RUN   TestSign
--- PASS: TestSign (0.00s)
---       runtime: cycle=639.4k imports=3 allocs=17.1M(3.41%)
ok      ./examples/gno.land/p/demo/int256 	0.59s
```

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [TODO] Added new tests, or not needed, or not feasible
- [ ] 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
- [ ] Provided any useful hints for running manual tests
- [TODO] 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).
</details>
  • Loading branch information
r3v4s authored Mar 29, 2024
1 parent 9336c7f commit 8ae1e7f
Show file tree
Hide file tree
Showing 15 changed files with 1,978 additions and 0 deletions.
21 changes: 21 additions & 0 deletions examples/gno.land/p/demo/int256/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Trịnh Đức Bảo Linh(Kevin)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
6 changes: 6 additions & 0 deletions examples/gno.land/p/demo/int256/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Fixed size signed 256-bit math library

1. This is a library specialized at replacing the big.Int library for math based on signed 256-bit types.
2. It uses [uint256](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/uint256) as the underlying type.

ported from [mempooler/int256](https://github.com/mempooler/int256)
20 changes: 20 additions & 0 deletions examples/gno.land/p/demo/int256/absolute.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package int256

import (
"gno.land/p/demo/uint256"
)

// Abs returns |z|
func (z *Int) Abs() *uint256.Uint {
return z.abs.Clone()
}

// AbsGt returns true if |z| > x, where x is a uint256
func (z *Int) AbsGt(x *uint256.Uint) bool {
return z.abs.Gt(x)
}

// AbsLt returns true if |z| < x, where x is a uint256
func (z *Int) AbsLt(x *uint256.Uint) bool {
return z.abs.Lt(x)
}
105 changes: 105 additions & 0 deletions examples/gno.land/p/demo/int256/absolute_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package int256

import (
"testing"

"gno.land/p/demo/uint256"
)

func TestAbs(t *testing.T) {
tests := []struct {
x, want string
}{
{"0", "0"},
{"1", "1"},
{"-1", "1"},
{"-2", "2"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935"},
}

for _, tc := range tests {
x, err := FromDecimal(tc.x)
if err != nil {
t.Error(err)
continue
}

got := x.Abs()

if got.ToString() != tc.want {
t.Errorf("Abs(%s) = %v, want %v", tc.x, got.ToString(), tc.want)
}
}
}

func TestAbsGt(t *testing.T) {
tests := []struct {
x, y, want string
}{
{"0", "0", "false"},
{"1", "0", "true"},
{"-1", "0", "true"},
{"-1", "1", "false"},
{"-2", "1", "true"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "true"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "true"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"},
}

for _, tc := range tests {
x, err := FromDecimal(tc.x)
if err != nil {
t.Error(err)
continue
}

y, err := uint256.FromDecimal(tc.y)
if err != nil {
t.Error(err)
continue
}

got := x.AbsGt(y)

if got != (tc.want == "true") {
t.Errorf("AbsGt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want)
}
}
}

func TestAbsLt(t *testing.T) {
tests := []struct {
x, y, want string
}{
{"0", "0", "false"},
{"1", "0", "false"},
{"-1", "0", "false"},
{"-1", "1", "false"},
{"-2", "1", "false"},
{"-5", "10", "true"},
{"31330", "31337", "true"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "0", "false"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "false"},
{"-115792089237316195423570985008687907853269984665640564039457584007913129639935", "115792089237316195423570985008687907853269984665640564039457584007913129639935", "false"},
}

for _, tc := range tests {
x, err := FromDecimal(tc.x)
if err != nil {
t.Error(err)
continue
}

y, err := uint256.FromDecimal(tc.y)
if err != nil {
t.Error(err)
continue
}

got := x.AbsLt(y)

if got != (tc.want == "true") {
t.Errorf("AbsLt(%s, %s) = %v, want %v", tc.x, tc.y, got, tc.want)
}
}
}
198 changes: 198 additions & 0 deletions examples/gno.land/p/demo/int256/arithmetic.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package int256

import (
"gno.land/p/demo/uint256"
)

func (z *Int) Add(x, y *Int) *Int {
z.initiateAbs()

neg := x.neg

if x.neg == y.neg {
// x + y == x + y
// (-x) + (-y) == -(x + y)
z.abs = z.abs.Add(x.abs, y.abs)
} else {
// x + (-y) == x - y == -(y - x)
// (-x) + y == y - x == -(x - y)
if x.abs.Cmp(y.abs) >= 0 {
z.abs = z.abs.Sub(x.abs, y.abs)
} else {
neg = !neg
z.abs = z.abs.Sub(y.abs, x.abs)
}
}
z.neg = neg // 0 has no sign
return z
}

// AddUint256 set z to the sum x + y, where y is a uint256, and returns z
func (z *Int) AddUint256(x *Int, y *uint256.Uint) *Int {
if x.neg {
if x.abs.Gt(y) {
z.abs.Sub(x.abs, y)
z.neg = true
} else {
z.abs.Sub(y, x.abs)
z.neg = false
}
} else {
z.abs.Add(x.abs, y)
z.neg = false
}
return z
}

// Sets z to the sum x + y, where z and x are uint256s and y is an int256.
func AddDelta(z, x *uint256.Uint, y *Int) {
if y.neg {
z.Sub(x, y.abs)
} else {
z.Add(x, y.abs)
}
}

// Sets z to the sum x + y, where z and x are uint256s and y is an int256.
func AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool {
var overflow bool
if y.neg {
_, overflow = z.SubOverflow(x, y.abs)
} else {
_, overflow = z.AddOverflow(x, y.abs)
}
return overflow
}

// Sub sets z to the difference x-y and returns z.
func (z *Int) Sub(x, y *Int) *Int {
z.initiateAbs()

neg := x.neg
if x.neg != y.neg {
// x - (-y) == x + y
// (-x) - y == -(x + y)
z.abs = z.abs.Add(x.abs, y.abs)
} else {
// x - y == x - y == -(y - x)
// (-x) - (-y) == y - x == -(x - y)
if x.abs.Cmp(y.abs) >= 0 {
z.abs = z.abs.Sub(x.abs, y.abs)
} else {
neg = !neg
z.abs = z.abs.Sub(y.abs, x.abs)
}
}
z.neg = neg // 0 has no sign
return z
}

// SubUint256 set z to the difference x - y, where y is a uint256, and returns z
func (z *Int) SubUint256(x *Int, y *uint256.Uint) *Int {
if x.neg {
z.abs.Add(x.abs, y)
z.neg = true
} else {
if x.abs.Lt(y) {
z.abs.Sub(y, x.abs)
z.neg = true
} else {
z.abs.Sub(x.abs, y)
z.neg = false
}
}
return z
}

// Mul sets z to the product x*y and returns z.
func (z *Int) Mul(x, y *Int) *Int {
z.initiateAbs()

z.abs = z.abs.Mul(x.abs, y.abs)
z.neg = x.neg != y.neg // 0 has no sign
return z
}

// MulUint256 sets z to the product x*y, where y is a uint256, and returns z
func (z *Int) MulUint256(x *Int, y *uint256.Uint) *Int {
z.abs.Mul(x.abs, y)
if z.abs.IsZero() {
z.neg = false
} else {
z.neg = x.neg
}
return z
}

// Div sets z to the quotient x/y for y != 0 and returns z.
func (z *Int) Div(x, y *Int) *Int {
z.initiateAbs()

z.abs.Div(x.abs, y.abs)
if x.neg == y.neg {
z.neg = false
} else {
z.neg = true
}
return z
}

// DivUint256 sets z to the quotient x/y, where y is a uint256, and returns z
// If y == 0, z is set to 0
func (z *Int) DivUint256(x *Int, y *uint256.Uint) *Int {
z.abs.Div(x.abs, y)
if z.abs.IsZero() {
z.neg = false
} else {
z.neg = x.neg
}
return z
}

// Quo sets z to the quotient x/y for y != 0 and returns z.
// If y == 0, a division-by-zero run-time panic occurs.
// OBS: differs from mempooler int256, we need to panic manually if y == 0
// Quo implements truncated division (like Go); see QuoRem for more details.
func (z *Int) Quo(x, y *Int) *Int {
if y.IsZero() {
panic("division by zero")
}

z.initiateAbs()

z.abs = z.abs.Div(x.abs, y.abs)
z.neg = !(z.abs.IsZero()) && x.neg != y.neg // 0 has no sign
return z
}

// Rem sets z to the remainder x%y for y != 0 and returns z.
// If y == 0, a division-by-zero run-time panic occurs.
// OBS: differs from mempooler int256, we need to panic manually if y == 0
// Rem implements truncated modulus (like Go); see QuoRem for more details.
func (z *Int) Rem(x, y *Int) *Int {
if y.IsZero() {
panic("division by zero")
}

z.initiateAbs()

z.abs.Mod(x.abs, y.abs)
z.neg = z.abs.Sign() > 0 && x.neg // 0 has no sign
return z
}

// Mod sets z to the modulus x%y for y != 0 and returns z.
// If y == 0, z is set to 0 (OBS: differs from the big.Int)
func (z *Int) Mod(x, y *Int) *Int {
if x.neg {
z.abs.Div(x.abs, y.abs)
z.abs.Add(z.abs, one)
z.abs.Mul(z.abs, y.abs)
z.abs.Sub(z.abs, x.abs)
z.abs.Mod(z.abs, y.abs)
} else {
z.abs.Mod(x.abs, y.abs)
}
z.neg = false
return z
}
Loading

0 comments on commit 8ae1e7f

Please sign in to comment.