diff --git a/examples/gno.land/p/demo/int256/LICENSE b/examples/gno.land/p/demo/int256/LICENSE deleted file mode 100644 index fc7e78a4875..00000000000 --- a/examples/gno.land/p/demo/int256/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -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. \ No newline at end of file diff --git a/examples/gno.land/p/demo/int256/README.md b/examples/gno.land/p/demo/int256/README.md deleted file mode 100644 index be467471199..00000000000 --- a/examples/gno.land/p/demo/int256/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# 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) diff --git a/examples/gno.land/p/demo/int256/absolute.gno b/examples/gno.land/p/demo/int256/absolute.gno deleted file mode 100644 index 825dd60c62a..00000000000 --- a/examples/gno.land/p/demo/int256/absolute.gno +++ /dev/null @@ -1,18 +0,0 @@ -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) -} diff --git a/examples/gno.land/p/demo/int256/absolute_test.gno b/examples/gno.land/p/demo/int256/absolute_test.gno deleted file mode 100644 index 55f6e41d0c8..00000000000 --- a/examples/gno.land/p/demo/int256/absolute_test.gno +++ /dev/null @@ -1,105 +0,0 @@ -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) - } - } -} diff --git a/examples/gno.land/p/demo/int256/arithmetic.gno b/examples/gno.land/p/demo/int256/arithmetic.gno index 8926fe1d6de..afdb2bde90e 100644 --- a/examples/gno.land/p/demo/int256/arithmetic.gno +++ b/examples/gno.land/p/demo/int256/arithmetic.gno @@ -1,202 +1,396 @@ package int256 -import "gno.land/p/demo/uint256" +import ( + "gno.land/p/demo/uint256" +) -func (z *Int) Add(x, y *Int) *Int { - z.initiateAbs() - - if x.neg == y.neg { - // If both numbers have the same sign, add their absolute values - z.abs.Add(x.abs, y.abs) - z.neg = x.neg - } else { - switch x.abs.Cmp(y.abs) { - case 1: // x > y - z.abs.Sub(x.abs, y.abs) - z.neg = x.neg - case -1: // x < y - z.abs.Sub(y.abs, x.abs) - z.neg = y.neg - case 0: // x == y - z.abs = uint256.NewUint(0) - } - } +var divisionByZeroError = "division by zero" +// Add adds two int256 values and saves the result in z. +func (z *Int) Add(x, y *Int) *Int { + z.value.Add(&x.value, &y.value) return z } -// AddUint256 set z to the sum x + y, where y is a uint256, and returns z +// AddUint256 adds int256 and uint256 values and saves the result in 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 - } + z.value.Add(&x.value, y) 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. +// Sub subtracts two int256 values and saves the result in z. func (z *Int) Sub(x, y *Int) *Int { - z.initiateAbs() - - if x.neg != y.neg { - // If sign are different, add the absolute values - z.abs.Add(x.abs, y.abs) - z.neg = x.neg - } else { - switch x.abs.Cmp(y.abs) { - case 1: // x > y - z.abs.Sub(x.abs, y.abs) - z.neg = x.neg - case -1: // x < y - z.abs.Sub(y.abs, x.abs) - z.neg = !x.neg - case 0: // x == y - z.abs = uint256.NewUint(0) - } - } - - // Ensure zero is always positive - if z.abs.IsZero() { - z.neg = false - } + z.value.Sub(&x.value, &y.value) return z } -// SubUint256 set z to the difference x - y, where y is a uint256, and returns z +// SubUint256 subtracts uint256 and int256 values and saves the result in 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 - } - } + z.value.Sub(&x.value, y) return z } -// Mul sets z to the product x*y and returns z. +// Mul multiplies two int256 values and saves the result in z. +// +// It considers the signs of the operands to determine the sign of the result. func (z *Int) Mul(x, y *Int) *Int { - z.initiateAbs() + xAbs, xSign := x.Abs(), x.Sign() + yAbs, ySign := y.Abs(), y.Sign() + + z.value.Mul(xAbs, yAbs) + + if xSign != ySign { + z.value.Neg(&z.value) + } - z.abs = z.abs.Mul(x.abs, y.abs) - z.neg = x.neg != y.neg && !z.abs.IsZero() // 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 +// Abs returns the absolute value of z. +func (z *Int) Abs() *uint256.Uint { + if z.Sign() >= 0 { + return &z.value } - return z + + var absValue uint256.Uint + absValue.Sub(uint0, &z.value).Neg(&z.value) + + return &absValue } -// Div sets z to the quotient x/y for y != 0 and returns z. -func (z *Int) Div(x, y *Int) *Int { - z.initiateAbs() +/* +--------------------------------------------------------------------------+ +// |Division and modulus operations | +// +--------------------------------------------------------------------------+ +// +// This package provides three different division and modulus operations: +// - Div and Rem: Truncated division (T-division) +// - Quo and Mod: Floored division (F-division) +// - DivE and ModE: Euclidean division (E-division) +// +// * Truncated division (Div, Rem) is the most common implementation in modern processors +// and programming languages. It rounds quotients towards zero and the remainder +// always has the same sign as the dividend. +// +// * Floored division (Quo, Mod) always rounds quotients towards negative infinity. +// This ensures that the modulus is always non-negative for a positive divisor, +// which can be useful in certain algorithms. +// +// * Euclidean division (DivE, ModE) ensures that the remainder is always non-negative, +// regardless of the signs of the dividend and divisor. This has several mathematical +// advantages: +// 1. It satisfies the unique division with remainder theorem. +// 2. It preserves division and modulus properties for negative divisors. +// 3. It allows for optimizations in divisions by powers of two. +// [+] Currently, ModE and Mod are shared the same implementation. +// +// ## Performance considerations: +// - For most operations, the performance difference between these division types is negligible. +// - Euclidean division may require an extra comparison and potentially an addition, +// which could impact performance in extremely performance-critical scenarios. +// - For divisions by powers of two, Euclidean division can be optimized to use +// bitwise operations, potentially offering better performance. +// +// ## Usage guidelines: +// - Use Div and Rem for general-purpose division that matches most common expectations. +// - Use Quo and Mod when you need a non-negative remainder for positive divisors, +// or when implementing algorithms that assume floored division. +// - Use DivE and ModE when you need the mathematical properties of Euclidean division, +// or when working with algorithms that specifically require it. +// +// Note: When working with negative numbers, be aware of the differences in behavior +// between these division types, especially at the boundaries of integer ranges. +// +// ## References +// 1. Daan Leijen, “Division and Modulus for Computer Scientists” (https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf) +/* ---------------------------------------------------------------------------- */ - if y.abs.IsZero() { - panic("division by zero") +// Div performs integer division z = x / y and returns z. +// If y == 0, it panics with a "division by zero" error. +// +// This function handles signed division using two's complement representation: +// 1. Determine the sign of the quotient based on the signs of x and y. +// 2. Perform unsigned division on the absolute values. +// 3. Adjust the result's sign if necessary. +// +// Example visualization for 8-bit integers (scaled down from 256-bit for simplicity): +// +// Let x = -6 (11111010 in two's complement) and y = 3 (00000011) +// +// Step 2: Determine signs +// +// x: negative (MSB is 1) +// y: positive (MSB is 0) +// +// Step 3: Calculate absolute values +// +// |x| = 6: 11111010 -> 00000110 +// NOT: 00000101 +// +1: 00000110 +// +// |y| = 3: 00000011 (already positive) +// +// Step 4: Unsigned division +// +// 6 / 3 = 2: 00000010 +// +// Step 5: Adjust sign (x and y have different signs) +// +// -2: 00000010 -> 11111110 +// NOT: 11111101 +// +1: 11111110 +// +// Note: This implementation rounds towards zero, as is standard in Go. +func (z *Int) Div(x, y *Int) *Int { + // Step 1: Check for division by zero + if y.IsZero() { + panic(divisionByZeroError) } - z.abs.Div(x.abs, y.abs) - z.neg = (x.neg != y.neg) && !z.abs.IsZero() // 0 has no sign + // Step 2, 3: Calculate the absolute values of x and y + xAbs, xSign := x.Abs(), x.Sign() + yAbs, ySign := y.Abs(), y.Sign() - return z -} + // Step 4: Perform unsigned division on the absolute values + z.value.Div(xAbs, yAbs) -// 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 + // Step 5: Adjust the sign of the result + // if x and y have different signs, the result must be negative + if xSign != ySign { + z.value.Neg(&z.value) } + 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. +// Example visualization for 8-bit integers (scaled down from 256-bit for simplicity): +// +// Let x = -7 (11111001 in two's complement) and y = 3 (00000011) +// +// Step 2: Determine signs +// +// x: negative (MSB is 1) +// y: positive (MSB is 0) +// +// Step 3: Calculate absolute values +// +// |x| = 7: 11111001 -> 00000111 +// NOT: 00000110 +// +1: 00000111 +// +// |y| = 3: 00000011 (already positive) +// +// Step 4: Unsigned division +// +// 7 / 3 = 2: 00000010 +// +// Step 5: Adjust sign (x and y have different signs) +// +// -2: 00000010 -> 11111110 +// NOT: 11111101 +// +1: 11111110 +// +// Final result: -2 (11111110 in two's complement) +// +// Note: This implementation rounds towards zero, as is standard in Go. func (z *Int) Quo(x, y *Int) *Int { + // Step 1: Check for division by zero if y.IsZero() { - panic("division by zero") + panic(divisionByZeroError) } - z.initiateAbs() + // Step 2, 3: Calculate the absolute values of x and y + xAbs, xSign := x.Abs(), x.Sign() + yAbs, ySign := y.Abs(), y.Sign() + + // perform unsigned division on the absolute values + z.value.Div(xAbs, yAbs) + + // Step 5: Adjust the sign of the result + // if x and y have different signs, the result must be negative + if xSign != ySign { + z.value.Neg(&z.value) + } - 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. +// +// The function performs the following steps: +// 1. Check for division by zero +// 2. Determine the signs of x and y +// 3. Calculate the absolute values of x and y +// 4. Perform unsigned division and get the remainder +// 5. Adjust the sign of the remainder +// +// Example visualization for 8-bit integers (scaled down from 256-bit for simplicity): +// +// Let x = -7 (11111001 in two's complement) and y = 3 (00000011) +// +// Step 2: Determine signs +// +// x: negative (MSB is 1) +// y: positive (MSB is 0) +// +// Step 3: Calculate absolute values +// +// |x| = 7: 11111001 -> 00000111 +// NOT: 00000110 +// +1: 00000111 +// +// |y| = 3: 00000011 (already positive) +// +// Step 4: Unsigned division +// +// 7 / 3 = 2 remainder 1 +// q = 2: 00000010 (not used in result) +// r = 1: 00000001 +// +// Step 5: Adjust sign of remainder (x is negative) +// +// -1: 00000001 -> 11111111 +// NOT: 11111110 +// +1: 11111111 +// +// Final result: -1 (11111111 in two's complement) +// +// Note: The sign of the remainder is always the same as the sign of the dividend (x). func (z *Int) Rem(x, y *Int) *Int { + // Step 1: Check for division by zero if y.IsZero() { - panic("division by zero") + panic(divisionByZeroError) } - z.initiateAbs() + // Step 2, 3 + xAbs, xSign := x.Abs(), x.Sign() + yAbs := y.Abs() - z.abs.Mod(x.abs, y.abs) - z.neg = z.abs.Sign() > 0 && x.neg // 0 has no sign + // Step 4: Perform unsigned division and get the remainder + var q, r uint256.Uint + q.DivMod(xAbs, yAbs, &r) + + // Step 5: Adjust the sign of the remainder + if xSign < 0 { + r.Neg(&r) + } + + z.value.Set(&r) 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) +// The result (z) has the same sign as the divisor y. 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) + return z.ModE(x, y) +} + +// DivE performs Euclidean division of x by y, setting z to the quotient and returning z. +// If y == 0, it panics with a "division by zero" error. +// +// Euclidean division satisfies the following properties: +// 1. The remainder is always non-negative: 0 <= x mod y < |y| +// 2. It follows the identity: x = y * (x div y) + (x mod y) +func (z *Int) DivE(x, y *Int) *Int { + if y.IsZero() { + panic(divisionByZeroError) } - z.neg = false + + // Compute the truncated division quotient + z.Quo(x, y) + + // Compute the remainder + r := new(Int).Rem(x, y) + + // If the remainder is negative, adjust the quotient + if r.Sign() < 0 { + if y.Sign() > 0 { + z.Sub(z, NewInt(1)) + } else { + z.Add(z, NewInt(1)) + } + } + return z } + +// ModE computes the Euclidean modulus of x by y, setting z to the result and returning z. +// If y == 0, it panics with a "division by zero" error. +// +// The Euclidean modulus is always non-negative and satisfies: +// +// 0 <= x mod y < |y| +// +// Example visualization for 8-bit integers (scaled down from 256-bit for simplicity): +// +// Case 1: Let x = -7 (11111001 in two's complement) and y = 3 (00000011) +// +// Step 1: Compute remainder (using Rem) +// +// Result of Rem: -1 (11111111 in two's complement) +// +// Step 2: Adjust sign (result is negative, y is positive) +// +// -1 + 3 = 2 +// 11111111 + 00000011 = 00000010 +// +// Final result: 2 (00000010) +// +// Case 2: Let x = -7 (11111001 in two's complement) and y = -3 (11111101 in two's complement) +// +// Step 1: Compute remainder (using Rem) +// +// Result of Rem: -1 (11111111 in two's complement) +// +// Step 2: Adjust sign (result is negative, y is negative) +// +// No adjustment needed +// +// Final result: -1 (11111111 in two's complement) +// +// Note: This implementation ensures that the result always has the same sign as y, +// which is different from the Rem operation. +func (z *Int) ModE(x, y *Int) *Int { + if y.IsZero() { + panic(divisionByZeroError) + } + + // Perform T-division to get the remainder + z.Rem(x, y) + + // Adjust the remainder if necessary + if z.Sign() >= 0 { + return z + } + if y.Sign() > 0 { + return z.Add(z, y) + } + + return z.Sub(z, y) +} + +// Sets z to the sum x + y, where z and x are uint256s and y is an int256. +// +// If the y is positive, it adds y.value to x. otherwise, it subtracts y.Abs() from x. +func AddDelta(z, x *uint256.Uint, y *Int) { + if y.Sign() >= 0 { + z.Add(x, &y.value) + } else { + z.Sub(x, y.Abs()) + } +} + +// Sets z to the sum x + y, where z and x are uint256s and y is an int256. +// +// This function returns true if the addition overflows, false otherwise. +func AddDeltaOverflow(z, x *uint256.Uint, y *Int) bool { + var overflow bool + if y.Sign() >= 0 { + _, overflow = z.AddOverflow(x, &y.value) + } else { + var absY uint256.Uint + absY.Sub(uint0, &y.value) // absY = -y.value + _, overflow = z.SubOverflow(x, &absY) + } + + return overflow +} diff --git a/examples/gno.land/p/demo/int256/arithmetic_test.gno b/examples/gno.land/p/demo/int256/arithmetic_test.gno index 4cfa306890a..3b36fb56dcd 100644 --- a/examples/gno.land/p/demo/int256/arithmetic_test.gno +++ b/examples/gno.land/p/demo/int256/arithmetic_test.gno @@ -276,6 +276,8 @@ func TestMul(t *testing.T) { {"5", "-3", "-15"}, {"0", "3", "0"}, {"3", "0", "0"}, + {"-5", "-3", "15"}, + {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "1", "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, } for _, tc := range tests { @@ -306,50 +308,6 @@ func TestMul(t *testing.T) { } } -func TestMulUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"1", "0", "0"}, - {"1", "1", "1"}, - {"1", "2", "2"}, - {"-1", "1", "-1"}, - {"-1", "3", "-3"}, - {"3", "4", "12"}, - {"-3", "4", "-12"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-115792089237316195423570985008687907853269984665640564039457584007913129639932"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "115792089237316195423570985008687907853269984665640564039457584007913129639932"}, - } - - 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 - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.MulUint256(x, y) - - if got.Neq(want) { - t.Errorf("MulUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - func TestDiv(t *testing.T) { tests := []struct { x, y, expected string @@ -364,7 +322,9 @@ func TestDiv(t *testing.T) { {"-10", "3", "-3"}, {"7", "3", "2"}, {"-7", "3", "-2"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, // Max uint256 / 2 + // the maximum value of a positive number in int256 is less than the maximum value of a uint256 + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "2", "28948022309329048855892746252171976963317496166410141009864396001978282409983"}, // (Max int256 - 1) / 2 + {"-57896044618658097711785492504343953926634992332820282019728792003956564819967", "2", "-28948022309329048855892746252171976963317496166410141009864396001978282409983"}, // (Min int256 + 1) / 2 } for _, tt := range tests { @@ -375,9 +335,6 @@ func TestDiv(t *testing.T) { if result.ToString() != tt.expected { t.Errorf("Div(%s, %s) = %s, want %s", tt.x, tt.y, result.ToString(), tt.expected) } - if result.abs.IsZero() && result.neg { - t.Errorf("Div(%s, %s) resulted in negative zero", tt.x, tt.y) - } }) } @@ -393,51 +350,6 @@ func TestDiv(t *testing.T) { }) } -func TestDivUint256(t *testing.T) { - tests := []struct { - x, y, want string - }{ - {"0", "1", "0"}, - {"1", "0", "0"}, - {"1", "1", "1"}, - {"1", "2", "0"}, - {"-1", "1", "-1"}, - {"-1", "3", "0"}, - {"4", "3", "1"}, - {"25", "5", "5"}, - {"25", "4", "6"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "-57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639934", "2", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - } - - 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 - } - - want, err := FromDecimal(tc.want) - if err != nil { - t.Error(err) - continue - } - - got := New() - got.DivUint256(x, y) - - if got.Neq(want) { - t.Errorf("DivUint256(%s, %s) = %v, want %v", tc.x, tc.y, got.ToString(), want.ToString()) - } - } -} - func TestQuo(t *testing.T) { tests := []struct { x, y, want string @@ -449,6 +361,7 @@ func TestQuo(t *testing.T) { {"-10", "1", "-10"}, {"-10", "-1", "10"}, {"10", "-3", "-3"}, + {"-10", "3", "-3"}, {"10", "3", "3"}, } @@ -530,10 +443,8 @@ func TestMod(t *testing.T) { }{ {"0", "1", "0"}, {"0", "-1", "0"}, - {"10", "0", "0"}, {"10", "1", "0"}, {"10", "-1", "0"}, - {"-10", "0", "0"}, {"-10", "1", "0"}, {"-10", "-1", "0"}, {"10", "3", "1"}, @@ -569,3 +480,134 @@ func TestMod(t *testing.T) { } } } + +func TestModPanic(t *testing.T) { + tests := []struct { + x, y string + }{ + {"10", "0"}, + {"10", "-0"}, + {"-10", "0"}, + {"-10", "-0"}, + } + + for _, tc := range tests { + defer func() { + if r := recover(); r == nil { + t.Errorf("Mod(%s, %s) did not panic", tc.x, tc.y) + } + }() + x, err := FromDecimal(tc.x) + if err != nil { + t.Error(err) + continue + } + + y, err := FromDecimal(tc.y) + if err != nil { + t.Error(err) + continue + } + + result := New().Mod(x, y) + t.Errorf("Mod(%s, %s) = %v, want %v", tc.x, tc.y, result.ToString(), "0") + } +} + +func TestDivE(t *testing.T) { + testCases := []struct { + x, y int64 + want int64 + }{ + {8, 3, 2}, + {8, -3, -2}, + {-8, 3, -3}, + {-8, -3, 3}, + {1, 2, 0}, + {1, -2, 0}, + {-1, 2, -1}, + {-1, -2, 1}, + {0, 1, 0}, + {0, -1, 0}, + } + + for _, tc := range testCases { + x := NewInt(tc.x) + y := NewInt(tc.y) + want := NewInt(tc.want) + got := new(Int).DivE(x, y) + if got.Cmp(want) != 0 { + t.Errorf("DivE(%v, %v) = %v, want %v", tc.x, tc.y, got, want) + } + } +} + +func TestDivEByZero(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("DivE did not panic on division by zero") + } + }() + + x := NewInt(1) + y := NewInt(0) + new(Int).DivE(x, y) +} + +func TestModEByZero(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("ModE did not panic on division by zero") + } + }() + + x := NewInt(1) + y := NewInt(0) + new(Int).ModE(x, y) +} + +func TestLargeNumbers(t *testing.T) { + x, _ := new(Int).SetString("123456789012345678901234567890") + y, _ := new(Int).SetString("987654321098765432109876543210") + + // Expected results (calculated separately) + expectedQ, _ := new(Int).SetString("0") + expectedR, _ := new(Int).SetString("123456789012345678901234567890") + + gotQ := new(Int).DivE(x, y) + gotR := new(Int).ModE(x, y) + + if gotQ.Cmp(expectedQ) != 0 { + t.Errorf("DivE with large numbers: got %v, want %v", gotQ, expectedQ) + } + + if gotR.Cmp(expectedR) != 0 { + t.Errorf("ModE with large numbers: got %v, want %v", gotR, expectedR) + } +} + +func TestAbs(t *testing.T) { + tests := []struct { + x, want string + }{ + {"0", "0"}, + {"1", "1"}, + {"-1", "1"}, + {"-2", "2"}, + {"-100000000000", "100000000000"}, + } + + 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) + } + } +} diff --git a/examples/gno.land/p/demo/int256/bitwise.gno b/examples/gno.land/p/demo/int256/bitwise.gno index c0d0f65f78f..1a1fe2e9720 100644 --- a/examples/gno.land/p/demo/int256/bitwise.gno +++ b/examples/gno.land/p/demo/int256/bitwise.gno @@ -1,94 +1,54 @@ package int256 -import ( - "gno.land/p/demo/uint256" -) - -// Or sets z = x | y and returns z. -func (z *Int) Or(x, y *Int) *Int { - if x.neg == y.neg { - if x.neg { - // (-x) | (-y) == ^(x-1) | ^(y-1) == ^((x-1) & (y-1)) == -(((x-1) & (y-1)) + 1) - x1 := new(uint256.Uint).Sub(x.abs, one) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.And(x1, y1), one) - z.neg = true // z cannot be zero if x and y are negative - return z - } - - // x | y == x | y - z.abs = z.abs.Or(x.abs, y.abs) - z.neg = false - return z - } - - // x.neg != y.neg - if x.neg { - x, y = y, x // | is symmetric - } - - // x | (-y) == x | ^(y-1) == ^((y-1) &^ x) == -(^((y-1) &^ x) + 1) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.AndNot(y1, x.abs), one) - z.neg = true // z cannot be zero if one of x or y is negative - +// Not sets z to the bitwise NOT of x and returns z. +// +// The bitwise NOT operation flips each bit of the operand. +func (z *Int) Not(x *Int) *Int { + z.value.Not(&x.value) return z } -// And sets z = x & y and returns z. +// And sets z to the bitwise AND of x and y and returns z. +// +// The bitwise AND operation results in a value that has a bit set +// only if both corresponding bits of the operands are set. func (z *Int) And(x, y *Int) *Int { - if x.neg == y.neg { - if x.neg { - // (-x) & (-y) == ^(x-1) & ^(y-1) == ^((x-1) | (y-1)) == -(((x-1) | (y-1)) + 1) - x1 := new(uint256.Uint).Sub(x.abs, one) - y1 := new(uint256.Uint).Sub(y.abs, one) - z.abs = z.abs.Add(z.abs.Or(x1, y1), one) - z.neg = true // z cannot be zero if x and y are negative - return z - } - - // x & y == x & y - z.abs = z.abs.And(x.abs, y.abs) - z.neg = false - return z - } + z.value.And(&x.value, &y.value) + return z +} - // x.neg != y.neg - // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1192-1202;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 - if x.neg { - x, y = y, x // & is symmetric - } +// Or sets z to the bitwise OR of x and y and returns z. +// +// The bitwise OR operation results in a value that has a bit set +// if at least one of the corresponding bits of the operands is set. +func (z *Int) Or(x, y *Int) *Int { + z.value.Or(&x.value, &y.value) + return z +} - // x & (-y) == x & ^(y-1) == x &^ (y-1) - y1 := new(uint256.Uint).Sub(y.abs, uint256.One()) - z.abs = z.abs.AndNot(x.abs, y1) - z.neg = false +// Xor sets z to the bitwise XOR of x and y and returns z. +// +// The bitwise XOR operation results in a value that has a bit set +// only if the corresponding bits of the operands are different. +func (z *Int) Xor(x, y *Int) *Int { + z.value.Xor(&x.value, &y.value) return z } -// Rsh sets z = x >> n and returns z. -// OBS: Different from original implementation it was using math.Big +// Rsh sets z to the result of right-shifting x by n bits and returns z. +// +// Right shift operation moves all bits in the operand to the right by the specified number of positions. +// Bits shifted out on the right are discarded, and zeros are shifted in on the left. func (z *Int) Rsh(x *Int, n uint) *Int { - if !x.neg { - z.abs.Rsh(x.abs, n) - z.neg = x.neg - return z - } - - // REF: https://cs.opensource.google/go/go/+/refs/tags/go1.22.1:src/math/big/int.go;l=1118-1126;drc=d57303e65f00b84b528ee682747dbe1fd3316d30 - t := NewInt(0).Sub(FromUint256(x.abs), NewInt(1)) - t = t.Rsh(t, n) - - _tmp := t.Add(t, NewInt(1)) - z.abs = _tmp.Abs() - z.neg = true - + z.value.Rsh(&x.value, n) return z } -// Lsh sets z = x << n and returns z. +// Lsh sets z to the result of left-shifting x by n bits and returns z. +// +// Left shift operation moves all bits in the operand to the left by the specified number of positions. +// Bits shifted out on the left are discarded, and zeros are shifted in on the right. func (z *Int) Lsh(x *Int, n uint) *Int { - z.abs.Lsh(x.abs, n) - z.neg = x.neg + z.value.Lsh(&x.value, n) return z } diff --git a/examples/gno.land/p/demo/int256/bitwise_test.gno b/examples/gno.land/p/demo/int256/bitwise_test.gno index 8dc16cd17ac..6e3d4a92a9b 100644 --- a/examples/gno.land/p/demo/int256/bitwise_test.gno +++ b/examples/gno.land/p/demo/int256/bitwise_test.gno @@ -2,198 +2,134 @@ package int256 import ( "testing" - - "gno.land/p/demo/uint256" ) -func TestOr(t *testing.T) { +func TestBitwise_And(t *testing.T) { tests := []struct { - name string - x, y, want Int + x, y, want string }{ - { - name: "all zeroes", - x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "all ones", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - }, - { - name: "mixed", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - }, - { - name: "one operand all ones", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - }, + {"5", "1", "1"}, // 0101 & 0001 = 0001 + {"-1", "1", "1"}, // 1111 & 0001 = 0001 + {"-5", "3", "3"}, // 1111...1011 & 0000...0011 = 0000...0011 } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := New() - got.Or(&tc.x, &tc.y) - - if got.Neq(&tc.want) { - t.Errorf("Or(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) - } - }) + x, _ := FromDecimal(tc.x) + y, _ := FromDecimal(tc.y) + want, _ := FromDecimal(tc.want) + + got := new(Int).And(x, y) + + if got.Neq(want) { + t.Errorf("And(%s, %s) = %s, want %s", x.ToString(), y.ToString(), got.ToString(), want.ToString()) + } } } -func TestAnd(t *testing.T) { +func TestBitwise_Or(t *testing.T) { tests := []struct { - name string - x, y, want Int + x, y, want string }{ - { - name: "all zeroes", - x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "all ones", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - }, - { - name: "mixed", - x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "mixed 2", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "mixed 3", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "one operand zero", - x: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0, 0, 0, 0}}, neg: false}, - }, - { - name: "one operand all ones", - x: Int{abs: &uint256.Uint{arr: [4]uint64{^uint64(0), ^uint64(0), ^uint64(0), ^uint64(0)}}, neg: false}, - y: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, - want: Int{abs: &uint256.Uint{arr: [4]uint64{0x5555555555555555, 0xAAAAAAAAAAAAAAAA, 0xFFFFFFFFFFFFFFFF, 0x0000000000000000}}, neg: false}, - }, + {"5", "1", "5"}, // 0101 | 0001 = 0101 + {"-1", "1", "-1"}, // 1111 | 0001 = 1111 + {"-5", "3", "-5"}, // 1111...1011 | 0000...0011 = 1111...1011 } for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got := New() - got.And(&tc.x, &tc.y) - - if got.Neq(&tc.want) { - t.Errorf("And(%v, %v) = %v, want %v", tc.x, tc.y, got, tc.want) - } - }) + x, _ := FromDecimal(tc.x) + y, _ := FromDecimal(tc.y) + want, _ := FromDecimal(tc.want) + + got := new(Int).Or(x, y) + + if got.Neq(want) { + t.Errorf("Or(%s, %s) = %s, want %s", x.ToString(), y.ToString(), got.ToString(), want.ToString()) + } } } -func TestRsh(t *testing.T) { +func TestBitwise_Not(t *testing.T) { tests := []struct { - x string - n uint - want string + x, want string }{ - {"1024", 0, "1024"}, - {"1024", 1, "512"}, - {"1024", 2, "256"}, - {"1024", 10, "1"}, - {"1024", 11, "0"}, - {"18446744073709551615", 0, "18446744073709551615"}, - {"18446744073709551615", 1, "9223372036854775807"}, - {"18446744073709551615", 62, "3"}, - {"18446744073709551615", 63, "1"}, - {"18446744073709551615", 64, "0"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 0, "115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 1, "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 128, "340282366920938463463374607431768211455"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 255, "1"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", 256, "0"}, - {"-1024", 0, "-1024"}, - {"-1024", 1, "-512"}, - {"-1024", 2, "-256"}, - {"-1024", 10, "-1"}, - {"-1024", 10, "-1"}, - {"-9223372036854775808", 0, "-9223372036854775808"}, - {"-9223372036854775808", 1, "-4611686018427387904"}, - {"-9223372036854775808", 62, "-2"}, - {"-9223372036854775808", 63, "-1"}, - {"-9223372036854775808", 64, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 0, "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 1, "-28948022309329048855892746252171976963317496166410141009864396001978282409984"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 253, "-4"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 254, "-2"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 255, "-1"}, - {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", 256, "-1"}, + {"5", "-6"}, // 0101 -> 1111...1010 + {"-1", "0"}, // 1111...1111 -> 0000...0000 } for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue + x, _ := FromDecimal(tc.x) + want, _ := FromDecimal(tc.want) + + got := new(Int).Not(x) + + if got.Neq(want) { + t.Errorf("Not(%s) = %s, want %s", x.ToString(), got.ToString(), want.ToString()) } + } +} + +func TestBitwise_Xor(t *testing.T) { + tests := []struct { + x, y, want string + }{ + {"5", "1", "4"}, // 0101 ^ 0001 = 0100 + {"-1", "1", "-2"}, // 1111...1111 ^ 0000...0001 = 1111...1110 + {"-5", "3", "-8"}, // 1111...1011 ^ 0000...0011 = 1111...1000 + } - got := New() - got.Rsh(x, tc.n) + for _, tt := range tests { + x, _ := FromDecimal(tt.x) + y, _ := FromDecimal(tt.y) + want, _ := FromDecimal(tt.want) - if got.ToString() != tc.want { - t.Errorf("Rsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) + got := new(Int).Xor(x, y) + + if got.Neq(want) { + t.Errorf("Xor(%s, %s) = %s, want %s", x.ToString(), y.ToString(), got.ToString(), want.ToString()) } } } -func TestLsh(t *testing.T) { +func TestBitwise_Rsh(t *testing.T) { tests := []struct { x string n uint want string }{ - {"1", 0, "1"}, - {"1", 1, "2"}, - {"1", 2, "4"}, - {"2", 0, "2"}, - {"2", 1, "4"}, - {"2", 2, "8"}, - {"-2", 0, "-2"}, - {"-4", 0, "-4"}, - {"-8", 0, "-8"}, + {"5", 1, "2"}, // 0101 >> 1 = 0010 + {"42", 3, "5"}, // 00101010 >> 3 = 00000101 } - for _, tc := range tests { - x, err := FromDecimal(tc.x) - if err != nil { - t.Error(err) - continue + for _, tt := range tests { + x, _ := FromDecimal(tt.x) + want, _ := FromDecimal(tt.want) + + got := new(Int).Rsh(x, tt.n) + + if got.Neq(want) { + t.Errorf("Rsh(%s, %d) = %s, want %s", x.ToString(), tt.n, got.ToString(), want.ToString()) } + } +} + +func TestBitwise_Lsh(t *testing.T) { + tests := []struct { + x string + n uint + want string + }{ + {"5", 2, "20"}, // 0101 << 2 = 10100 + {"42", 5, "1344"}, // 00101010 << 5 = 10101000000 + } + + for _, tt := range tests { + x, _ := FromDecimal(tt.x) + want, _ := FromDecimal(tt.want) - got := New() - got.Lsh(x, tc.n) + got := new(Int).Lsh(x, tt.n) - if got.ToString() != tc.want { - t.Errorf("Lsh(%s, %d) = %v, want %v", tc.x, tc.n, got.ToString(), tc.want) + if got.Neq(want) { + t.Errorf("Lsh(%s, %d) = %s, want %s", x.ToString(), tt.n, got.ToString(), want.ToString()) } } } diff --git a/examples/gno.land/p/demo/int256/cmp.gno b/examples/gno.land/p/demo/int256/cmp.gno index 426dfd76485..c91a25568e9 100644 --- a/examples/gno.land/p/demo/int256/cmp.gno +++ b/examples/gno.land/p/demo/int256/cmp.gno @@ -1,86 +1,59 @@ package int256 -// Eq returns true if z == x func (z *Int) Eq(x *Int) bool { - return (z.neg == x.neg) && z.abs.Eq(x.abs) + return z.value.Eq(&x.value) } -// Neq returns true if z != x func (z *Int) Neq(x *Int) bool { return !z.Eq(x) } -// Cmp compares x and y and returns: +// Cmp compares z and x and returns: // -// -1 if x < y -// 0 if x == y -// +1 if x > y -func (z *Int) Cmp(x *Int) (r int) { - // x cmp y == x cmp y - // x cmp (-y) == x - // (-x) cmp y == y - // (-x) cmp (-y) == -(x cmp y) - switch { - case z == x: - // nothing to do - case z.neg == x.neg: - r = z.abs.Cmp(x.abs) - if z.neg { - r = -r - } - case z.neg: - r = -1 - default: - r = 1 +// - 1 if z > x +// - 0 if z == x +// - -1 if z < x +func (z *Int) Cmp(x *Int) int { + zSign, xSign := z.Sign(), x.Sign() + + if zSign == xSign { + return z.value.Cmp(&x.value) } - return + + if zSign == 0 { + return -xSign + } + + return zSign } // IsZero returns true if z == 0 func (z *Int) IsZero() bool { - return z.abs.IsZero() + return z.value.IsZero() } // IsNeg returns true if z < 0 func (z *Int) IsNeg() bool { - return z.neg + return z.Sign() < 0 } -// Lt returns true if z < x func (z *Int) Lt(x *Int) bool { - if z.neg { - if x.neg { - return z.abs.Gt(x.abs) - } else { - return true - } - } else { - if x.neg { - return false - } else { - return z.abs.Lt(x.abs) - } - } + return z.Cmp(x) < 0 } -// Gt returns true if z > x func (z *Int) Gt(x *Int) bool { - if z.neg { - if x.neg { - return z.abs.Lt(x.abs) - } else { - return false - } - } else { - if x.neg { - return true - } else { - return z.abs.Gt(x.abs) - } - } + return z.Cmp(x) > 0 +} + +func (z *Int) Le(x *Int) bool { + return z.Cmp(x) <= 0 +} + +func (z *Int) Ge(x *Int) bool { + return z.Cmp(x) >= 0 } // Clone creates a new Int identical to z func (z *Int) Clone() *Int { - return &Int{z.abs.Clone(), z.neg} + return New().FromUint256(&z.value) } diff --git a/examples/gno.land/p/demo/int256/cmp_test.gno b/examples/gno.land/p/demo/int256/cmp_test.gno index 81b9231babe..b552a84ba8a 100644 --- a/examples/gno.land/p/demo/int256/cmp_test.gno +++ b/examples/gno.land/p/demo/int256/cmp_test.gno @@ -85,7 +85,7 @@ func TestCmp(t *testing.T) { {"-1", "0", -1}, {"0", "-1", 1}, {"1", "1", 0}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, + // {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", 1}, } for _, tc := range tests { @@ -140,7 +140,7 @@ func TestIsNeg(t *testing.T) { want bool }{ {"0", false}, - {"-0", true}, // TODO: should this be false? + {"-0", false}, {"1", false}, {"-1", true}, {"10", false}, @@ -173,7 +173,6 @@ func TestLt(t *testing.T) { {"0", "-1", false}, {"1", "1", false}, {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", false}, } for _, tc := range tests { @@ -208,7 +207,6 @@ func TestGt(t *testing.T) { {"0", "-1", true}, {"1", "1", false}, {"-1", "-1", false}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935", "-115792089237316195423570985008687907853269984665640564039457584007913129639935", true}, } for _, tc := range tests { @@ -232,21 +230,19 @@ func TestGt(t *testing.T) { } func TestClone(t *testing.T) { - tests := []struct { - x string - }{ - {"0"}, - {"-0"}, - {"1"}, - {"-1"}, - {"10"}, - {"-10"}, - {"115792089237316195423570985008687907853269984665640564039457584007913129639935"}, - {"-115792089237316195423570985008687907853269984665640564039457584007913129639935"}, + tests := []string{ + "0", + "-0", + "1", + "-1", + "10", + "-10", + "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "-115792089237316195423570985008687907853269984665640564039457584007913129639935", } - for _, tc := range tests { - x, err := FromDecimal(tc.x) + for _, xStr := range tests { + x, err := FromDecimal(xStr) if err != nil { t.Error(err) continue @@ -254,8 +250,8 @@ func TestClone(t *testing.T) { y := x.Clone() - if x.Cmp(y) != 0 { - t.Errorf("Clone(%s) = %v, want %v", tc.x, y, x) + if x.Neq(y) { + t.Errorf("cloned value is not equal to original value") } } } diff --git a/examples/gno.land/p/demo/int256/conversion.gno b/examples/gno.land/p/demo/int256/conversion.gno index 9e264e7e46b..7da74ea2c2e 100644 --- a/examples/gno.land/p/demo/int256/conversion.gno +++ b/examples/gno.land/p/demo/int256/conversion.gno @@ -1,87 +1,108 @@ package int256 -import "gno.land/p/demo/uint256" +import ( + "math" -// SetInt64 sets z to x and returns z. -func (z *Int) SetInt64(x int64) *Int { - z.initiateAbs() + "gno.land/p/demo/uint256" +) - neg := false - if x < 0 { - neg = true - x = -x - } - if z.abs == nil { - panic("abs is nil") +// SetInt64 sets the Int to the value of the provided int64. +// +// This method allows for easy conversion from standard Go integer types +// to Int, correctly handling both positive and negative values. +func (z *Int) SetInt64(v int64) *Int { + if v >= 0 { + z.value.SetUint64(uint64(v)) + } else { + z.value.SetUint64(uint64(-v)).Neg(&z.value) } - z.abs = z.abs.SetUint64(uint64(x)) - z.neg = neg return z } -// SetUint64 sets z to x and returns z. -func (z *Int) SetUint64(x uint64) *Int { - z.initiateAbs() - - if z.abs == nil { - panic("abs is nil") - } - z.abs = z.abs.SetUint64(x) - z.neg = false +// SetUint64 sets the Int to the value of the provided uint64. +func (z *Int) SetUint64(v uint64) *Int { + z.value.SetUint64(v) return z } // Uint64 returns the lower 64-bits of z func (z *Int) Uint64() uint64 { - return z.abs.Uint64() + if z.Sign() < 0 { + panic("cannot convert negative int256 to uint64") + } + if z.value.Gt(uint256.NewUint(0).SetUint64(math.MaxUint64)) { + panic("overflow: int256 does not fit in uint64 type") + } + return z.value.Uint64() } // Int64 returns the lower 64-bits of z func (z *Int) Int64() int64 { - _abs := z.abs.Clone() - - if z.neg { - return -int64(_abs.Uint64()) + if z.Sign() >= 0 { + if z.value.BitLen() > 64 { + panic("overflow: int256 does not fit in int64 type") + } + return int64(z.value.Uint64()) + } else { + var temp uint256.Uint + temp.Sub(uint256.NewUint(0), &z.value) // temp = -z.value + if temp.BitLen() > 64 { + panic("overflow: int256 does not fit in int64 type") + } + return -int64(temp.Uint64()) } - return int64(_abs.Uint64()) } // Neg sets z to -x and returns z.) func (z *Int) Neg(x *Int) *Int { - z.abs.Set(x.abs) - if z.abs.IsZero() { - z.neg = false + if x.IsZero() { + z.value.Clear() } else { - z.neg = !x.neg + z.value.Neg(&x.value) } return z } // Set sets z to x and returns z. func (z *Int) Set(x *Int) *Int { - z.abs.Set(x.abs) - z.neg = x.neg + z.value.Set(&x.value) return z } // SetFromUint256 converts a uint256.Uint to Int and sets the value to z. func (z *Int) SetUint256(x *uint256.Uint) *Int { - z.abs.Set(x) - z.neg = false + z.value.Set(x) return z } -// OBS, differs from original mempooler int256 -// ToString returns the decimal representation of z. +// ToString returns a string representation of z in base 10. +// The string is prefixed with a minus sign if z is negative. func (z *Int) ToString() string { - if z == nil { - panic("int256: nil pointer to ToString()") + if z.value.IsZero() { + return "0" } - - t := z.abs.Dec() - if z.neg { - return "-" + t + sign := z.Sign() + var temp uint256.Uint + if sign >= 0 { + temp.Set(&z.value) + } else { + // temp = -z.value + temp.Sub(uint256.NewUint(0), &z.value) + } + s := temp.Dec() + if sign < 0 { + return "-" + s } + return s +} - return t +// NilToZero returns the Int if it's not nil, or a new zero-valued Int otherwise. +// +// This method is useful for safely handling potentially nil Int pointers, +// ensuring that operations always have a valid Int to work with. +func (z *Int) NilToZero() *Int { + if z == nil { + return Zero() + } + return z } diff --git a/examples/gno.land/p/demo/int256/conversion_test.gno b/examples/gno.land/p/demo/int256/conversion_test.gno index b085a77a15a..5b8c5fe456a 100644 --- a/examples/gno.land/p/demo/int256/conversion_test.gno +++ b/examples/gno.land/p/demo/int256/conversion_test.gno @@ -8,43 +8,20 @@ import ( func TestSetInt64(t *testing.T) { tests := []struct { - x int64 - want string + v int64 + expect int }{ - {0, "0"}, - {1, "1"}, - {-1, "-1"}, - {9223372036854775807, "9223372036854775807"}, - {-9223372036854775808, "-9223372036854775808"}, + {0, 0}, + {1, 1}, + {-1, -1}, + {9223372036854775807, 1}, // overflow (max int64) + {-9223372036854775808, -1}, // underflow (min int64) } - for _, tc := range tests { - var z Int - z.SetInt64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetInt64(%d) = %s, want %s", tc.x, got, tc.want) - } - } -} - -func TestSetUint64(t *testing.T) { - tests := []struct { - x uint64 - want string - }{ - {0, "0"}, - {1, "1"}, - } - - for _, tc := range tests { - var z Int - z.SetUint64(tc.x) - - got := z.ToString() - if got != tc.want { - t.Errorf("SetUint64(%d) = %s, want %s", tc.x, got, tc.want) + for _, tt := range tests { + z := New().SetInt64(tt.v) + if z.Sign() != tt.expect { + t.Errorf("SetInt64(%d) = %d, want %d", tt.v, z.Sign(), tt.expect) } } } @@ -59,24 +36,39 @@ func TestUint64(t *testing.T) { {"9223372036854775807", 9223372036854775807}, {"9223372036854775808", 9223372036854775808}, {"18446744073709551615", 18446744073709551615}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, - {"-1", 1}, - {"-18446744073709551615", 18446744073709551615}, - {"-18446744073709551616", 0}, - {"-18446744073709551617", 1}, } - for _, tc := range tests { - z := MustFromDecimal(tc.x) + for _, tt := range tests { + z := MustFromDecimal(tt.x) got := z.Uint64() - if got != tc.want { - t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + if got != tt.want { + t.Errorf("Uint64(%s) = %d, want %d", tt.x, got, tt.want) } } } +func TestUint64_Panic(t *testing.T) { + tests := []struct { + x string + }{ + {"-1"}, + {"18446744073709551616"}, + {"18446744073709551617"}, + } + + for _, tt := range tests { + defer func() { + if r := recover(); r == nil { + t.Errorf("Uint64(%s) did not panic", tt.x) + } + }() + + z := MustFromDecimal(tt.x) + z.Uint64() + } +} + func TestInt64(t *testing.T) { tests := []struct { x string @@ -85,22 +77,40 @@ func TestInt64(t *testing.T) { {"0", 0}, {"1", 1}, {"9223372036854775807", 9223372036854775807}, - {"18446744073709551616", 0}, - {"18446744073709551617", 1}, {"-1", -1}, {"-9223372036854775808", -9223372036854775808}, } - for _, tc := range tests { - z := MustFromDecimal(tc.x) + for _, tt := range tests { + z := MustFromDecimal(tt.x) got := z.Int64() - if got != tc.want { - t.Errorf("Uint64(%s) = %d, want %d", tc.x, got, tc.want) + if got != tt.want { + t.Errorf("Uint64(%s) = %d, want %d", tt.x, got, tt.want) } } } +func TestInt64_Panic(t *testing.T) { + tests := []struct { + x string + }{ + {"18446744073709551616"}, + {"18446744073709551617"}, + } + + for _, tt := range tests { + defer func() { + if r := recover(); r == nil { + t.Errorf("Int64(%s) did not panic", tt.x) + } + }() + + z := MustFromDecimal(tt.x) + z.Int64() + } +} + func TestNeg(t *testing.T) { tests := []struct { x string @@ -113,13 +123,13 @@ func TestNeg(t *testing.T) { {"-18446744073709551615", "18446744073709551615"}, } - for _, tc := range tests { - z := MustFromDecimal(tc.x) + for _, tt := range tests { + z := MustFromDecimal(tt.x) z.Neg(z) got := z.ToString() - if got != tc.want { - t.Errorf("Neg(%s) = %s, want %s", tc.x, got, tc.want) + if got != tt.want { + t.Errorf("Neg(%s) = %s, want %s", tt.x, got, tt.want) } } } @@ -136,13 +146,13 @@ func TestSet(t *testing.T) { {"-18446744073709551615", "-18446744073709551615"}, } - for _, tc := range tests { - z := MustFromDecimal(tc.x) + for _, tt := range tests { + z := MustFromDecimal(tt.x) z.Set(z) got := z.ToString() - if got != tc.want { - t.Errorf("Set(%s) = %s, want %s", tc.x, got, tc.want) + if got != tt.want { + t.Errorf("Set(%s) = %s, want %s", tt.x, got, tt.want) } } } @@ -158,77 +168,54 @@ func TestSetUint256(t *testing.T) { {"18446744073709551615", "18446744073709551615"}, } - for _, tc := range tests { + for _, tt := range tests { got := New() - z := uint256.MustFromDecimal(tc.x) + z := uint256.MustFromDecimal(tt.x) got.SetUint256(z) - if got.ToString() != tc.want { - t.Errorf("SetUint256(%s) = %s, want %s", tc.x, got.ToString(), tc.want) + if got.ToString() != tt.want { + t.Errorf("SetUint256(%s) = %s, want %s", tt.x, got.ToString(), tt.want) } } } func TestToString(t *testing.T) { tests := []struct { - name string - setup func() *Int + input string expected string }{ - { - name: "Zero from subtraction", - setup: func() *Int { - minusThree := MustFromDecimal("-3") - three := MustFromDecimal("3") - return Zero().Add(minusThree, three) - }, - expected: "0", - }, - { - name: "Zero from right shift", - setup: func() *Int { - return Zero().Rsh(One(), 1234) - }, - expected: "0", - }, - { - name: "Positive number", - setup: func() *Int { - return MustFromDecimal("42") - }, - expected: "42", - }, - { - name: "Negative number", - setup: func() *Int { - return MustFromDecimal("-42") - }, - expected: "-42", - }, - { - name: "Large positive number", - setup: func() *Int { - return MustFromDecimal("115792089237316195423570985008687907853269984665640564039457584007913129639935") - }, - expected: "115792089237316195423570985008687907853269984665640564039457584007913129639935", - }, - { - name: "Large negative number", - setup: func() *Int { - return MustFromDecimal("-115792089237316195423570985008687907853269984665640564039457584007913129639935") - }, - expected: "-115792089237316195423570985008687907853269984665640564039457584007913129639935", - }, + {"0", "0"}, + {"1", "1"}, + {"-1", "-1"}, + {"123456789", "123456789"}, + {"-123456789", "-123456789"}, + {"18446744073709551615", "18446744073709551615"}, // max uint64 + {"-18446744073709551615", "-18446744073709551615"}, + {"340282366920938463463374607431768211455", "340282366920938463463374607431768211455"}, // max uint128 + {"-340282366920938463463374607431768211455", "-340282366920938463463374607431768211455"}, + {"-57896044618658097711785492504343953926634992332820282019728792003956564819968", "-57896044618658097711785492504343953926634992332820282019728792003956564819968"}, + {"57896044618658097711785492504343953926634992332820282019728792003956564819967", "57896044618658097711785492504343953926634992332820282019728792003956564819967"}, } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - z := tt.setup() - result := z.ToString() - if result != tt.expected { - t.Errorf("ToString() = %s, want %s", result, tt.expected) - } - }) + x, err := FromDecimal(tt.input) + if err != nil { + t.Errorf("Failed to parse input (%s): %v", tt.input, err) + continue + } + + output := x.ToString() + + if output != tt.expected { + t.Errorf("String(%s) = %s, want %s", tt.input, output, tt.expected) + } + } +} + +func TestNilToZero(t *testing.T) { + z := New().NilToZero() + if z.Sign() != 0 { + t.Errorf("NilToZero() = %d, want %d", z.Sign(), 0) } } diff --git a/examples/gno.land/p/demo/int256/doc.gno b/examples/gno.land/p/demo/int256/doc.gno new file mode 100644 index 00000000000..4481af54069 --- /dev/null +++ b/examples/gno.land/p/demo/int256/doc.gno @@ -0,0 +1,23 @@ +// The int256 package provides a 256-bit signed interger type for gno, +// supporting arithmetic operations and bitwise manipulation. +// +// It designed for applications that require high-precision arithmetic +// beyond the standard 64-bit range. +// +// ## Features +// +// - 256-bit Signed Integers: Support for large integer ranging from -2^255 tp 2^255-1. +// - Two's Complement Representation: Efficient storage and computation using two's complement. +// - Arithmetic Operations: Add, Sub, Mul, Div, Mod, Inc, Dec, etc. +// - Bitwise Operations: And, Or, Xor, Not, etc. +// - Comparison Operations: Cmp, Eq, Lt, Gt, etc. +// - Conversion Functions: Int to Uint, Uint to Int, etc. +// - String Parsing and Formatting: Convert to and from decimal string representation. +// +// ## Notes +// +// - Some methods may panic when encountering invalid inputs or overflows. +// - The `int256.Int` type can interact with `uint256.Uint` from the `p/demo/uint256` package. +// - Unlike `math/big.Int`, the `int256.Int` type has fixed size (256-bit) and does not support +// arbitrary precision arithmetic. +package int256 diff --git a/examples/gno.land/p/demo/int256/int256.gno b/examples/gno.land/p/demo/int256/int256.gno index caccd17d531..c283e11e0d3 100644 --- a/examples/gno.land/p/demo/int256/int256.gno +++ b/examples/gno.land/p/demo/int256/int256.gno @@ -1,64 +1,87 @@ -// This package provides a 256-bit signed integer type, Int, and associated functions. package int256 import ( + "errors" + "gno.land/p/demo/uint256" ) -var one = uint256.NewUint(1) +var ( + int1 = NewInt(1) + uint0 = uint256.NewUint(0) + uint1 = uint256.NewUint(1) +) type Int struct { - abs *uint256.Uint - neg bool + value uint256.Uint } -// Zero returns a new Int set to 0. -func Zero() *Int { - return NewInt(0) +// New creates and returns a new Int initialized to zero. +func New() *Int { + return &Int{} } -// One returns a new Int set to 1. -func One() *Int { - return NewInt(1) +// NewInt allocates and returns a new Int set to the value of the provided int64. +func NewInt(x int64) *Int { + return New().SetInt64(x) } -// Sign returns: +// Zero returns a new Int initialized to 0. // -// -1 if x < 0 -// 0 if x == 0 -// +1 if x > 0 -func (z *Int) Sign() int { - z.initiateAbs() +// This function is useful for creating a starting point for calculations or +// when an explicit zero value is needed. +func Zero() *Int { return &Int{} } - if z.abs.IsZero() { - return 0 - } - if z.neg { - return -1 - } - return 1 -} - -// New returns a new Int set to 0. -func New() *Int { +// One returns a new Int initialized to one. +// +// This function is convenient for operations that require a unit value, +// such as incrementing or serving as an identity element in multiplication. +func One() *Int { return &Int{ - abs: new(uint256.Uint), + value: *uint256.NewUint(1), } } -// NewInt allocates and returns a new Int set to x. -func NewInt(x int64) *Int { - return New().SetInt64(x) +// Sign determines the sign of the Int. +// +// It returns -1 for negative numbers, 0 for zero, and +1 for positive numbers. +func (z *Int) Sign() int { + if z.IsZero() { + return 0 + } + // Right shift the value by 255 bits to check the sign bit. + // In two's complement representation, the most significant bit (MSB) is the sign bit. + // If the MSB is 0, the number is positive; if it is 1, the number is negative. + // + // Example: + // Original value: 1 0 1 0 ... 0 1 (256 bits) + // After Rsh 255: 0 0 0 0 ... 0 1 (1 bit) + // + // This approach is highly efficient as it avoids the need for comparisons + // or arithmetic operations on the full 256-bit number. Instead it reduces + // the problem to checking a single bit. + // + // Additionally, this method will work correctly for all values, + // including the minimum possible negative number (which in two's complement + // doesn't have a positive counterpart in the same bit range). + var temp uint256.Uint + if temp.Rsh(&z.value, 255).IsZero() { + return 1 + } + return -1 } -// FromDecimal returns a new Int from a decimal string. -// Returns a new Int and an error if the string is not a valid decimal. +// FromDecimal creates a new Int from a decimal string representation. +// It handles both positive and negative values. +// +// This function is useful for parsing user input or reading numeric data +// from text-based formats. func FromDecimal(s string) (*Int, error) { - return new(Int).SetString(s) + return New().SetString(s) } -// MustFromDecimal returns a new Int from a decimal string. -// Panics if the string is not a valid decimal. +// MustFromDecimal is similar to FromDecimal but panics if the input string +// is not a valid decimal representation. func MustFromDecimal(s string) *Int { z, err := FromDecimal(s) if err != nil { @@ -67,60 +90,40 @@ func MustFromDecimal(s string) *Int { return z } -// SetString sets s to the value of z and returns z and a boolean indicating success. +// SetString sets the Int to the value represented by the input string. +// This method supports decimal string representations of integers and handles +// both positive and negative values. func (z *Int) SetString(s string) (*Int, error) { - neg := false - // Remove max one leading + - if len(s) > 0 && s[0] == '+' { - neg = false - s = s[1:] + if len(s) == 0 { + return nil, errors.New("cannot set int256 from empty string") } - if len(s) > 0 && s[0] == '-' { - neg = true + // Check for negative sign + neg := s[0] == '-' + if neg || s[0] == '+' { s = s[1:] } - var ( - abs *uint256.Uint - err error - ) - abs, err = uint256.FromDecimal(s) + + // Convert string to uint256 + temp, err := uint256.FromDecimal(s) if err != nil { return nil, err } - return &Int{ - abs, - neg, - }, nil -} - -// FromUint256 is a convenience-constructor from uint256.Uint. -// Returns a new Int and whether overflow occurred. -// OBS: If u is `nil`, this method returns `nil, false` -func FromUint256(x *uint256.Uint) *Int { - if x == nil { - return nil + // If negative, negate the uint256 value + if neg { + temp.Neg(temp) } - z := Zero() - z.SetUint256(x) - return z + z.value.Set(temp) + return z, nil } -// OBS, differs from original mempooler int256 -// NilToZero sets z to 0 and return it if it's nil, otherwise it returns z -func (z *Int) NilToZero() *Int { - if z == nil { - return NewInt(0) - } +// FromUint256 sets the Int to the value of the provided Uint256. +// +// This method allows for conversion from unsigned 256-bit integers +// to signed integers. +func (z *Int) FromUint256(v *uint256.Uint) *Int { + z.value.Set(v) return z } - -// initiateAbs sets default value for `z` or `z.abs` value if is nil -// OBS: differs from mempooler int256. It checks not only `z.abs` but also `z` -func (z *Int) initiateAbs() { - if z == nil || z.abs == nil { - z.abs = new(uint256.Uint) - } -} diff --git a/examples/gno.land/p/demo/int256/int256_test.gno b/examples/gno.land/p/demo/int256/int256_test.gno index 7c8181d1bec..375e6da0a45 100644 --- a/examples/gno.land/p/demo/int256/int256_test.gno +++ b/examples/gno.land/p/demo/int256/int256_test.gno @@ -1,7 +1,153 @@ -// ported from github.com/mempooler/int256 package int256 -import "testing" +import ( + "testing" + + "gno.land/p/demo/uint256" +) + +func TestInitializers(t *testing.T) { + tests := []struct { + name string + fn func() *Int + wantSign int + wantStr string + }{ + {"Zero", Zero, 0, "0"}, + {"New", New, 0, "0"}, + {"One", One, 1, "1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + z := tt.fn() + if z.Sign() != tt.wantSign { + t.Errorf("%s() = %d, want %d", tt.name, z.Sign(), tt.wantSign) + } + if z.ToString() != tt.wantStr { + t.Errorf("%s() = %s, want %s", tt.name, z.ToString(), tt.wantStr) + } + }) + } +} + +func TestNewInt(t *testing.T) { + tests := []struct { + input int64 + expected int + }{ + {0, 0}, + {1, 1}, + {-1, -1}, + {9223372036854775807, 1}, // max int64 + {-9223372036854775808, -1}, // min int64 + } + + for _, tt := range tests { + z := NewInt(tt.input) + if z.Sign() != tt.expected { + t.Errorf("NewInt(%d) = %d, want %d", tt.input, z.Sign(), tt.expected) + } + } +} + +func TestFromDecimal(t *testing.T) { + tests := []struct { + input string + expected int + isError bool + }{ + {"0", 0, false}, + {"1", 1, false}, + {"-1", -1, false}, + {"123456789", 1, false}, + {"-123456789", -1, false}, + {"invalid", 0, true}, + } + + for _, tt := range tests { + z, err := FromDecimal(tt.input) + if tt.isError { + if err == nil { + t.Errorf("FromDecimal(%s) expected error, but got nil", tt.input) + } + } else { + if err != nil { + t.Errorf("FromDecimal(%s) unexpected error: %v", tt.input, err) + } else if z.Sign() != tt.expected { + t.Errorf("FromDecimal(%s) sign is incorrect. Expected: %d, Actual: %d", tt.input, tt.expected, z.Sign()) + } + } + } +} + +func TestMustFromDecimal(t *testing.T) { + tests := []struct { + input string + expected int + shouldPanic bool + }{ + {"0", 0, false}, + {"1", 1, false}, + {"-1", -1, false}, + {"123", 1, false}, + {"invalid", 0, true}, + } + + for _, tt := range tests { + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("MustFromDecimal(%q) expected panic, but got nil", tt.input) + } + }() + } + + z := MustFromDecimal(tt.input) + if !tt.shouldPanic && z.Sign() != tt.expected { + t.Errorf("MustFromDecimal(%q) sign is incorrect. Expected: %d, Actual: %d", tt.input, tt.expected, z.Sign()) + } + } +} + +func TestSetUint64(t *testing.T) { + tests := []uint64{ + 0, + 1, + 18446744073709551615, // max uint64 + } + + for _, tt := range tests { + z := New().SetUint64(tt) + if z.Sign() < 0 { + t.Errorf("SetUint64(%d) result is negative", tt) + } + if tt == 0 && z.Sign() != 0 { + t.Errorf("SetUint64(0) result is not zero") + } + if tt > 0 && z.Sign() != 1 { + t.Errorf("SetUint64(%d) result is not positive", tt) + } + } +} + +func TestFromUint256(t *testing.T) { + tests := []struct { + input *uint256.Uint + expected int + }{ + {uint256.NewUint(0), 0}, + {uint256.NewUint(1), 1}, + {uint256.NewUint(18446744073709551615), 1}, + } + + for _, tt := range tests { + z := New().FromUint256(tt.input) + if z.Sign() != tt.expected { + t.Errorf("FromUint256(%v) = %d, want %d", tt.input, z.Sign(), tt.expected) + } + } +} func TestSign(t *testing.T) { tests := []struct { @@ -9,15 +155,59 @@ func TestSign(t *testing.T) { want int }{ {"0", 0}, + {"-0", 0}, + {"+0", 0}, {"1", 1}, {"-1", -1}, + {"9223372036854775807", 1}, + {"-9223372036854775808", -1}, } - for _, tc := range tests { - z := MustFromDecimal(tc.x) + for _, tt := range tests { + z := MustFromDecimal(tt.x) got := z.Sign() - if got != tc.want { - t.Errorf("Sign(%s) = %d, want %d", tc.x, got, tc.want) + if got != tt.want { + t.Errorf("Sign(%s) = %d, want %d", tt.x, got, tt.want) + } + } +} + +func BenchmarkSign(b *testing.B) { + z := New() + for i := 0; i < b.N; i++ { + z.SetUint64(uint64(i)) + z.Sign() + } +} + +func TestSetAndToString(t *testing.T) { + tests := []struct { + input string + expected int + isError bool + }{ + {"0", 0, false}, + {"1", 1, false}, + {"-1", -1, false}, + {"123456789", 1, false}, + {"-123456789", -1, false}, + {"invalid", 0, true}, + } + + for _, tt := range tests { + z, err := New().SetString(tt.input) + if tt.isError { + if err == nil { + t.Errorf("SetString(%s) expected error, but got nil", tt.input) + } + } else { + if err != nil { + t.Errorf("SetString(%s) unexpected error: %v", tt.input, err) + } else if z.Sign() != tt.expected { + t.Errorf("SetString(%s) sign is incorrect. Expected: %d, Actual: %d", tt.input, tt.expected, z.Sign()) + } else if z.ToString() != tt.input { + t.Errorf("SetString(%s) string representation is incorrect. Expected: %s, Actual: %s", tt.input, tt.input, z.ToString()) + } } } }