Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: holiman/uint256
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.3.0
Choose a base ref
...
head repository: holiman/uint256
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.3.2
Choose a head ref
  • 6 commits
  • 8 files changed
  • 4 contributors

Commits on Jul 25, 2024

  1. conversion: introduce alloc-free IntoBig method (#177)

    `ToBig` does 2x32 byte allocations. Once to create a new `big.Int` and a second time to seed the contents of the `big.Int`. This change adds `IntoBig` which takes an input `big.Int`, and, if possible, it will use the given `big.Int` buffer for conversion to avoid reallocation.
    
    If the big.Int is nil, it will be allocated (32 byte). If it's not large enough, it's data space will be allocated (32 byte).
    karalabe authored Jul 25, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    9fb9e97 View commit details
  2. divmod: fix aliasing error, add tests (#180)

    This change fixes a flaw in `DivMod` related to aliasing of input arguments.
    holiman authored Jul 25, 2024
    Copy the full SHA
    ce90883 View commit details

Commits on Oct 2, 2024

  1. conversion: fix unmarshalling overflow (#185)

    The json-unmarshalling from decimal form inorrectly invoked fromDecimal,
    instead of SetFromDecimal. The former assumes that the input has already
    been validated, and thus does not throw errors on overflow.
    
    Closes #184
    holiman authored Oct 2, 2024
    Copy the full SHA
    b07109b View commit details

Commits on Oct 12, 2024

  1. ci: update to run go1.23 on circle + update linter (#186)

    holiman authored Oct 12, 2024
    Copy the full SHA
    555918b View commit details

Commits on Nov 29, 2024

  1. uint256: optimize WriteToArray (#190)

    This change optimizes WriteToArray, and also implements PutUint256 for use in writing to "sufficiently large slices". 
    
    ---------
    
    Co-authored-by: Martin Holst Swende <martin@swende.se>
    minh-bq and holiman authored Nov 29, 2024
    Copy the full SHA
    439fbd4 View commit details

Commits on Dec 6, 2024

  1. optimize Exp for even bases (#189)

    ---------
    
    Co-authored-by: Martin Holst Swende <martin@swende.se>
    Valeh2012 and holiman authored Dec 6, 2024
    Copy the full SHA
    a17fcfb View commit details
Showing with 317 additions and 98 deletions.
  1. +74 −50 benchmarks_test.go
  2. +14 −6 circle.yml
  3. +53 −12 conversion.go
  4. +74 −0 conversion_test.go
  5. +2 −0 decimal.go
  6. +32 −1 ternary_test.go
  7. +54 −22 uint256.go
  8. +14 −7 uint256_test.go
124 changes: 74 additions & 50 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -572,10 +572,7 @@ func bigExp(result, base, exponent *big.Int) *big.Int {
return result
}

func benchmark_Exp_Big(bench *testing.B) {
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"

func benchmark_Exp_Big(bench *testing.B, x, y string) {
orig := big.NewInt(0).SetBytes(hex2Bytes(x))
base := big.NewInt(0).SetBytes(hex2Bytes(x))
exp := big.NewInt(0).SetBytes(hex2Bytes(y))
@@ -587,62 +584,55 @@ func benchmark_Exp_Big(bench *testing.B) {
base.Set(orig)
}
}
func benchmark_Exp_Bit(bench *testing.B) {
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
y := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"

base := big.NewInt(0).SetBytes(hex2Bytes(x))
exp := big.NewInt(0).SetBytes(hex2Bytes(y))

f_base, _ := FromBig(base)
f_orig, _ := FromBig(base)
f_exp, _ := FromBig(exp)
f_res := Int{}
func benchmark_Exp_U256(bench *testing.B, x, y string) {
var (
base = big.NewInt(0).SetBytes(hex2Bytes(x))
exp = big.NewInt(0).SetBytes(hex2Bytes(y))

f_base, _ = FromBig(base)
f_orig, _ = FromBig(base)
f_exp, _ = FromBig(exp)
f_res Int
)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
f_res.Exp(f_base, f_exp)
f_base.Set(f_orig)
}
}
func benchmark_ExpSmall_Big(bench *testing.B) {
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
y := "8abcdef"

orig := big.NewInt(0).SetBytes(hex2Bytes(x))
base := big.NewInt(0).SetBytes(hex2Bytes(x))
exp := big.NewInt(0).SetBytes(hex2Bytes(y))

result := new(big.Int)
bench.ResetTimer()
for i := 0; i < bench.N; i++ {
bigExp(result, base, exp)
base.Set(orig)
func BenchmarkExp(bench *testing.B) {
{ // Large values
base := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
exp := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
bench.Run("large/big", func(b *testing.B) {
benchmark_Exp_Big(b, base, exp)
})
bench.Run("large/uint256", func(b *testing.B) {
benchmark_Exp_U256(b, base, exp)
})
}
}
func benchmark_ExpSmall_Bit(bench *testing.B) {
x := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
y := "8abcdef"

base := big.NewInt(0).SetBytes(hex2Bytes(x))
exp := big.NewInt(0).SetBytes(hex2Bytes(y))

f_base, _ := FromBig(base)
f_orig, _ := FromBig(base)
f_exp, _ := FromBig(exp)
f_res := Int{}

bench.ResetTimer()
for i := 0; i < bench.N; i++ {
f_res.Exp(f_base, f_exp)
f_base.Set(f_orig)
{ // Smaller exponent
base := "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff"
exp := "8abcdef"
bench.Run("small/big", func(b *testing.B) {
benchmark_Exp_Big(b, base, exp)
})
bench.Run("small/uint256", func(b *testing.B) {
benchmark_Exp_U256(b, base, exp)
})
}
{ // Even base (and very small exponent)
base := "ABCDEF090807060504030201fffffffffffffffffffffffffffffffffffffffe"
exp := "ff"
bench.Run("even/big", func(b *testing.B) {
benchmark_Exp_Big(b, base, exp)
})
bench.Run("even/uint256", func(b *testing.B) {
benchmark_Exp_U256(b, base, exp)
})
}
}
func BenchmarkExp(bench *testing.B) {
bench.Run("large/big", benchmark_Exp_Big)
bench.Run("large/uint256", benchmark_Exp_Bit)
bench.Run("small/big", benchmark_ExpSmall_Big)
bench.Run("small/uint256", benchmark_ExpSmall_Bit)
}

func BenchmarkDiv(b *testing.B) {
@@ -968,7 +958,6 @@ func BenchmarkHashTreeRoot(b *testing.B) {
}

func BenchmarkSet(bench *testing.B) {

benchmarkUint256 := func(bench *testing.B) {
a := new(Int).SetBytes(hex2Bytes("f123456789abcdeffedcba9876543210f2f3f4f5f6f7f8f9fff3f4f5f6f7f8f9"))

@@ -1021,3 +1010,38 @@ func BenchmarkExtendSign(b *testing.B) {
result.ExtendSign(a, n)
}
}

func BenchmarkWriteTo(b *testing.B) {
fa, err := FromHex("0x1100030405060708090a0b0c0d0ed1e870eec79504c60144cc7f5fc2bad1e611")
if err != nil {
b.Fatal(err)
}
b.Run("fixed-20", func(b *testing.B) {
dest := [20]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
for i := 0; i < b.N; i++ {
fa.WriteToArray20(&dest)
}
_ = (string(dest[:])) // Prevent the compiler from optimizing away the op
})
b.Run("fixed-32", func(b *testing.B) {
dest := [32]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
for i := 0; i < b.N; i++ {
fa.WriteToArray32(&dest)
}
_ = (string(dest[:])) // Prevent the compiler from optimizing away the op
})
b.Run("slice", func(b *testing.B) {
dest := make([]byte, 64)
for i := 0; i < b.N; i++ {
fa.WriteToSlice(dest)
}
_ = (string(dest[:])) // Prevent the compiler from optimizing away the op
})
b.Run("put256", func(b *testing.B) {
dest := make([]byte, 64)
for i := 0; i < b.N; i++ {
fa.PutUint256(dest)
}
_ = (string(dest[:])) // Prevent the compiler from optimizing away the op
})
}
20 changes: 14 additions & 6 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -19,14 +19,14 @@ commands:
jobs:

go122:
go123:
docker:
- image: cimg/go:1.22
- image: cimg/go:1.23
steps:
- run:
name: "Install tools"
command: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.58.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.61.0
- checkout
- run:
name: "Lint"
@@ -82,6 +82,13 @@ jobs:
name: "Test (PPC64 emulation)"
command: qemu-ppc64-static uint256.test.ppc64 -test.v

go122:
docker:
- image: cimg/go:1.22
steps:
- checkout
- test

go121:
docker:
- image: cimg/go:1.21
@@ -112,9 +119,10 @@ workflows:
- go119
- go120
- go121
- go122:
- go122
- go123:
requires:
- go121
- go122
- bigendian:
requires:
- go122
- go123
65 changes: 53 additions & 12 deletions conversion.go
Original file line number Diff line number Diff line change
@@ -46,21 +46,62 @@ func (z *Int) ToBig() *big.Int {
if z == nil {
return nil
}
b := new(big.Int)
var b *big.Int
z.IntoBig(&b)
return b
}

// IntoBig sets a provided big.Int to the value of z.
// Sets `nil` if z is nil (thus the double pointer).
func (z *Int) IntoBig(b **big.Int) {
if z == nil {
*b = nil
return
}
if *b == nil {
*b = new(big.Int)
}
switch maxWords { // Compile-time check.
case 4: // 64-bit architectures.
words := [4]big.Word{big.Word(z[0]), big.Word(z[1]), big.Word(z[2]), big.Word(z[3])}
b.SetBits(words[:])
if words := (*b).Bits(); cap(words) >= 4 {
// Enough underlying space to set all the uint256 data
words = words[:4]

words[0] = big.Word(z[0])
words[1] = big.Word(z[1])
words[2] = big.Word(z[2])
words[3] = big.Word(z[3])

// Feed it back to normalize (up or down within the big.Int)
(*b).SetBits(words)
} else {
// Not enough space to set all the words, have to allocate
words := [4]big.Word{big.Word(z[0]), big.Word(z[1]), big.Word(z[2]), big.Word(z[3])}
(*b).SetBits(words[:])
}
case 8: // 32-bit architectures.
words := [8]big.Word{
big.Word(z[0]), big.Word(z[0] >> 32),
big.Word(z[1]), big.Word(z[1] >> 32),
big.Word(z[2]), big.Word(z[2] >> 32),
big.Word(z[3]), big.Word(z[3] >> 32),
if words := (*b).Bits(); cap(words) >= 8 {
// Enough underlying space to set all the uint256 data
words = words[:8]

words[0], words[1] = big.Word(z[0]), big.Word(z[0]>>32)
words[2], words[3] = big.Word(z[1]), big.Word(z[1]>>32)
words[4], words[5] = big.Word(z[2]), big.Word(z[2]>>32)
words[6], words[7] = big.Word(z[3]), big.Word(z[3]>>32)

// Feed it back to normalize (up or down within the big.Int)
(*b).SetBits(words)
} else {
// Not enough space to set all the words, have to allocate
words := [8]big.Word{
big.Word(z[0]), big.Word(z[0] >> 32),
big.Word(z[1]), big.Word(z[1] >> 32),
big.Word(z[2]), big.Word(z[2] >> 32),
big.Word(z[3]), big.Word(z[3] >> 32),
}
(*b).SetBits(words[:])
}
b.SetBits(words[:])
}
return b
}

// FromBig is a convenience-constructor from big.Int.
@@ -195,7 +236,7 @@ func (z *Int) UnmarshalText(input []byte) error {
if len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X') {
return z.fromHex(string(input))
}
return z.fromDecimal(string(input))
return z.SetFromDecimal(string(input))
}

// SetFromBig converts a big.Int to Int and sets the value to z.
@@ -671,7 +712,7 @@ func (z *Int) MarshalJSON() ([]byte, error) {
func (z *Int) UnmarshalJSON(input []byte) error {
if len(input) < 2 || input[0] != '"' || input[len(input)-1] != '"' {
// if not quoted, it must be decimal
return z.fromDecimal(string(input))
return z.SetFromDecimal(string(input))
}
return z.UnmarshalText(input[1 : len(input)-1])
}
74 changes: 74 additions & 0 deletions conversion_test.go
Original file line number Diff line number Diff line change
@@ -243,6 +243,30 @@ func TestToBig(t *testing.T) {
}
}

func TestIntoBig(t *testing.T) {
var uint256Nil *Int

bigNil := new(big.Int)
if uint256Nil.IntoBig(&bigNil); bigNil != nil {
t.Errorf("want big.Int <nil>, have %x", bigNil)
}
var bigZero *big.Int
if new(Int).IntoBig(&bigZero); bigZero.Cmp(new(big.Int)) != 0 {
t.Errorf("expected big.Int 0, got %x", bigZero)
}
var b *big.Int
for i := uint(0); i < 256; i++ {
f := new(Int).SetUint64(1)
f.Lsh(f, i)
f.IntoBig(&b)
expected := big.NewInt(1)
expected.Lsh(expected, i)
if b.Cmp(expected) != 0 {
t.Fatalf("expected %x, got %x", expected, b)
}
}
}

func BenchmarkScanScientific(b *testing.B) {
intsub1 := new(Int)
_ = intsub1.fromDecimal(twoPow256Sub1)
@@ -324,6 +348,28 @@ func BenchmarkToBig(bench *testing.B) {
bench.Run("4words", func(bench *testing.B) { benchToBig(bench, param4) })
}

func benchIntoBig(bench *testing.B, f *Int) *big.Int {
var b *big.Int
for i := 0; i < bench.N; i++ {
f.IntoBig(&b)
}
return b
}

func BenchmarkIntoBig(bench *testing.B) {
param1 := new(Int).SetUint64(0xff)
bench.Run("1word", func(bench *testing.B) { benchIntoBig(bench, param1) })

param2 := new(Int).Lsh(param1, 64)
bench.Run("2words", func(bench *testing.B) { benchIntoBig(bench, param2) })

param3 := new(Int).Lsh(param2, 64)
bench.Run("3words", func(bench *testing.B) { benchIntoBig(bench, param3) })

param4 := new(Int).Lsh(param3, 64)
bench.Run("4words", func(bench *testing.B) { benchIntoBig(bench, param4) })
}

func TestFormat(t *testing.T) {
testCases := []string{
"0",
@@ -1359,6 +1405,34 @@ func TestEnDecode(t *testing.T) {
}
}

func TestMarshallingErrors(t *testing.T) {
var check = func(repr string) string {
a := new(Int)
if err := json.Unmarshal([]byte(repr), a); err != nil {
return fmt.Sprintf("error: %v", err.Error())
}
return a.String()
}

for i, tc := range []string{
`0x1000000000000000000000000000000000000000000000000000000000000000`,
`0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`,
`0x10000000000000000000000000000000000000000000000000000000000000000`,
`0x10000000000000000000000000000000000000000000000000000000000000001`,
`0x111110000000000000000000000000000000000000000000000000000000000000001`,
} {
reference, ok := new(big.Int).SetString(tc, 0)
if !ok {
t.Fatalf("test %d: not ok input %q", i, tc)
}
haveHex := check(fmt.Sprintf(`"%#x"`, reference))
haveDec := check(fmt.Sprintf(`"%#d"`, reference))
if haveHex != haveDec {
t.Fatalf("test %d: hex unmarshal != dec unmarshal, \nhex -> %q\ndec -> %q\n", i, haveHex, haveDec)
}
}
}

func TestNil(t *testing.T) {
a := NewInt(1337)
if err := a.Scan(nil); err != nil {
Loading