From 09017bfd237b93565daab59dd15edf078957a622 Mon Sep 17 00:00:00 2001 From: Andrei Marinica Date: Sat, 24 Oct 2026 18:17:53 +0300 Subject: [PATCH] twos complement length moved to mx-components-big-int --- go.mod | 2 +- go.sum | 4 +- math/twosComplement.go | 47 ---------- math/twosComplement_test.go | 156 --------------------------------- vmhost/vmhooks/baseOps_test.go | 5 +- vmhost/vmhooks/bigIntOps.go | 4 +- 6 files changed, 7 insertions(+), 211 deletions(-) delete mode 100644 math/twosComplement.go delete mode 100644 math/twosComplement_test.go diff --git a/go.mod b/go.mod index 2d931f686..5dfd5c36e 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/multiversx/mx-chain-scenario-go v1.8.0 github.com/multiversx/mx-chain-storage-go v1.2.0 github.com/multiversx/mx-chain-vm-common-go v1.7.3 - github.com/multiversx/mx-components-big-int v1.2.0 + github.com/multiversx/mx-components-big-int v1.3.0 github.com/pelletier/go-toml v1.9.5 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v2 v2.27.7 diff --git a/go.sum b/go.sum index ffe9a3da4..52f7049a6 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/multiversx/mx-chain-storage-go v1.2.0 h1:c+T7NJdsnbZza5k7ez/pUEHeAT9B github.com/multiversx/mx-chain-storage-go v1.2.0/go.mod h1:jzaisAV6n/TdrQpYiMX/+A35yl9Cd8LAG+4Afqi6J5s= github.com/multiversx/mx-chain-vm-common-go v1.7.3 h1:vuxVaf02XV6rt2EOiF4HMGEVqYfykNghCD0chBSw+vA= github.com/multiversx/mx-chain-vm-common-go v1.7.3/go.mod h1:Lk2ZNxVCThYFbFhjXxy1S8PwqjcEnF7uvTQ0QvA4BbQ= -github.com/multiversx/mx-components-big-int v1.2.0 h1:PppI54XUxmghhFhsK4mT6pG/Ev9OuZKKlbKUfpsXklg= -github.com/multiversx/mx-components-big-int v1.2.0/go.mod h1:HhZ2YdqsweLoOXeSufOTmDOdfyckZ1uBDSnr8n4tOe8= +github.com/multiversx/mx-components-big-int v1.3.0 h1:YBAdMCWlK55Dr2u0qQQeX7QSjPkLCqQQYozdoFoa7ms= +github.com/multiversx/mx-components-big-int v1.3.0/go.mod h1:HhZ2YdqsweLoOXeSufOTmDOdfyckZ1uBDSnr8n4tOe8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/math/twosComplement.go b/math/twosComplement.go deleted file mode 100644 index 9d4858247..000000000 --- a/math/twosComplement.go +++ /dev/null @@ -1,47 +0,0 @@ -package math - -import "math/big" - -// UnsignedByteLength returns the number of bytes that the absolute value of the -// given big int occupies, i.e. len(bi.Bytes()), without allocating those bytes. -// -// Note that, just like big.Int.Bytes, this disregards the sign. -func UnsignedByteLength(bi *big.Int) int { - if bi == nil { - return 0 - } - return (bi.BitLen() + 7) / 8 -} - -// TwosComplementByteLength returns the number of bytes that the two's complement -// representation of the given value occupies, i.e. len(twos.ToBytes(bi)), -// without allocating or building that representation. -// -// The length is derived from the bit length alone: -// - zero is encoded on 0 bytes; -// - a positive value needs BitLen() bits plus one sign bit that must be 0, -// hence BitLen()/8 + 1 bytes; -// - a negative value -a is encoded as the bit-negation of a-1 plus a sign bit -// that must be 1, hence BitLen(a-1)/8 + 1 bytes. BitLen(a-1) equals -// BitLen(a), except when a is an exact power of two, when it is one less. -// -// TODO: move to mx-components-big-int/twos-complement after the next VM release. -func TwosComplementByteLength(bi *big.Int) int { - if bi == nil { - return 0 - } - sign := bi.Sign() - if sign == 0 { - return 0 - } - - // big.Int.BitLen and big.Int.TrailingZeroBits both refer to the absolute value - bitLen := bi.BitLen() - if sign < 0 && bi.TrailingZeroBits() == uint(bitLen-1) { - // the absolute value is an exact power of two, so subtracting 1 from it - // (as the two's complement encoding does) drops the leading bit - bitLen-- - } - - return bitLen/8 + 1 -} diff --git a/math/twosComplement_test.go b/math/twosComplement_test.go deleted file mode 100644 index edea1ad94..000000000 --- a/math/twosComplement_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package math - -import ( - "fmt" - "math/big" - "math/rand" - "testing" - - twos "github.com/multiversx/mx-components-big-int/twos-complement" - "github.com/stretchr/testify/require" -) - -func TestUnsignedByteLength_KnownValues(t *testing.T) { - t.Parallel() - - for _, tc := range []struct { - value int64 - expected int - }{ - {0, 0}, - {1, 1}, - {255, 1}, - {256, 2}, - {65535, 2}, - {65536, 3}, - {-1, 1}, - {-255, 1}, - {-256, 2}, - {-65536, 3}, - } { - require.Equal(t, tc.expected, UnsignedByteLength(big.NewInt(tc.value)), - "unexpected length for %d", tc.value) - } -} - -// the length must always agree with big.Int.Bytes, which is what -// BigIntGetUnsignedBytes writes out; the sign is disregarded by both -func TestUnsignedByteLength_MatchesBytes(t *testing.T) { - t.Parallel() - - forEachTestValue(t, func(value *big.Int) { - require.Equal(t, len(value.Bytes()), UnsignedByteLength(value), - "unexpected length for %s", value.String()) - }) -} - -// testing.AllocsPerRun cannot be used in a parallel test, hence no t.Parallel() here -func TestUnsignedByteLength_DoesNotAllocate(t *testing.T) { - positive := new(big.Int).Lsh(big.NewInt(1), 1<<20) - negative := new(big.Int).Neg(positive) - - allocations := testing.AllocsPerRun(100, func() { - _ = UnsignedByteLength(positive) - _ = UnsignedByteLength(negative) - }) - require.Zero(t, allocations) -} - -func TestTwosComplementByteLength_KnownValues(t *testing.T) { - t.Parallel() - - for _, tc := range []struct { - value int64 - expected int - }{ - {0, 0}, - {1, 1}, - {127, 1}, - {128, 2}, - {255, 2}, - {256, 2}, - {32767, 2}, - {32768, 3}, - {-1, 1}, - {-127, 1}, - {-128, 1}, - {-129, 2}, - {-255, 2}, - {-256, 2}, - {-257, 2}, - {-32768, 2}, - {-32769, 3}, - } { - require.Equal(t, tc.expected, TwosComplementByteLength(big.NewInt(tc.value)), - "unexpected length for %d", tc.value) - } -} - -// the length must always agree with the actual two's complement encoding -func TestTwosComplementByteLength_MatchesEncoding(t *testing.T) { - t.Parallel() - - forEachTestValue(t, func(value *big.Int) { - require.Equal(t, len(twos.ToBytes(value)), TwosComplementByteLength(value), - "unexpected length for %s", value.String()) - }) -} - -// forEachTestValue covers small values exhaustively, then the boundaries where the -// byte length changes (powers of two and their neighbours), then random values of -// every bit width; each value is checked with both signs. -func forEachTestValue(t *testing.T, assertMatches func(value *big.Int)) { - t.Run("small values, exhaustive", func(t *testing.T) { - for i := int64(-100000); i <= 100000; i++ { - assertMatches(big.NewInt(i)) - } - }) - - t.Run("powers of two and their neighbours", func(t *testing.T) { - for exponent := 0; exponent < 1024; exponent++ { - powerOfTwo := new(big.Int).Lsh(big.NewInt(1), uint(exponent)) - for _, delta := range []int64{-2, -1, 0, 1, 2} { - value := new(big.Int).Add(powerOfTwo, big.NewInt(delta)) - assertMatches(value) - assertMatches(new(big.Int).Neg(value)) - } - } - }) - - t.Run("random values of all sizes", func(t *testing.T) { - rng := rand.New(rand.NewSource(42)) - for bits := 1; bits <= 1024; bits++ { - limit := new(big.Int).Lsh(big.NewInt(1), uint(bits)) - for i := 0; i < 10; i++ { - value := new(big.Int).Rand(rng, limit) - assertMatches(value) - assertMatches(new(big.Int).Neg(value)) - } - } - }) -} - -// the whole point of the helper: no allocation, whatever the size of the value -// testing.AllocsPerRun cannot be used in a parallel test, hence no t.Parallel() here -func TestTwosComplementByteLength_DoesNotAllocate(t *testing.T) { - for _, bits := range []uint{64, 1 << 10, 1 << 16, 1 << 20} { - t.Run(fmt.Sprintf("%d bits", bits), func(t *testing.T) { - powerOfTwo := new(big.Int).Lsh(big.NewInt(1), bits) - // -2^bits is the worst case: all low words are zero, so the - // power-of-two check has to scan the entire value - values := []*big.Int{ - powerOfTwo, - new(big.Int).Neg(powerOfTwo), - new(big.Int).Sub(powerOfTwo, big.NewInt(12345)), - new(big.Int).Neg(new(big.Int).Sub(powerOfTwo, big.NewInt(12345))), - } - - allocations := testing.AllocsPerRun(100, func() { - for _, value := range values { - _ = TwosComplementByteLength(value) - } - }) - require.Zero(t, allocations) - }) - } -} diff --git a/vmhost/vmhooks/baseOps_test.go b/vmhost/vmhooks/baseOps_test.go index c7d5b259f..79921bc63 100644 --- a/vmhost/vmhooks/baseOps_test.go +++ b/vmhost/vmhooks/baseOps_test.go @@ -8,7 +8,6 @@ import ( "github.com/multiversx/mx-chain-vm-go/config" "github.com/multiversx/mx-chain-vm-go/executor" - "github.com/multiversx/mx-chain-vm-go/math" mockcontext "github.com/multiversx/mx-chain-vm-go/mock/context" twos "github.com/multiversx/mx-components-big-int/twos-complement" "github.com/stretchr/testify/assert" @@ -98,11 +97,11 @@ func TestBigIntByteLength_MatchesBytesHooks(t *testing.T) { for _, tc := range testCases { // BigIntGetUnsignedBytes / BigIntFinishUnsigned write value.Bytes() - require.Equal(t, len(tc.Bytes()), math.UnsignedByteLength(tc), + require.Equal(t, len(tc.Bytes()), twos.UnsignedByteLength(tc), "unsigned length mismatch for value %s", tc.String()) // BigIntGetSignedBytes / BigIntFinishSigned write twos.ToBytes(value) - require.Equal(t, len(twos.ToBytes(tc)), math.TwosComplementByteLength(tc), + require.Equal(t, len(twos.ToBytes(tc)), twos.ByteLength(tc), "signed length mismatch for value %s", tc.String()) } } diff --git a/vmhost/vmhooks/bigIntOps.go b/vmhost/vmhooks/bigIntOps.go index 1338ea3f3..dfb6c5f1e 100644 --- a/vmhost/vmhooks/bigIntOps.go +++ b/vmhost/vmhooks/bigIntOps.go @@ -337,7 +337,7 @@ func (context *VMHooksImpl) BigIntUnsignedByteLength(referenceHandle int32) int3 return -1 } - return int32(math.UnsignedByteLength(value)) + return int32(twos.UnsignedByteLength(value)) } // BigIntSignedByteLength VMHooks implementation. @@ -362,7 +362,7 @@ func (context *VMHooksImpl) BigIntSignedByteLength(referenceHandle int32) int32 // the two's complement representation is deliberately not built here: its length // can be computed from the bit length alone, and materializing it would allocate // proportionally to the value while only a flat gas cost is charged - return int32(math.TwosComplementByteLength(value)) + return int32(twos.ByteLength(value)) } // BigIntGetUnsignedBytes VMHooks implementation.