Skip to content

Commit

Permalink
conversion: optimize Scan() scientific scanning (#127)
Browse files Browse the repository at this point in the history
This change optimizes the Scan method, by making utilizing seek instead of string-split, adds some test and makes the function alloc-free. 

---------

Co-authored-by: Martin Holst Swende <[email protected]>
  • Loading branch information
elee1766 and holiman authored Mar 16, 2023
1 parent 146987f commit c385c61
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 25 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
/.idea


# go output files
*.test
*.out
*.pb.gz
57 changes: 32 additions & 25 deletions conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,33 +615,40 @@ func (dst *Int) Scan(src interface{}) error {
}
switch src := src.(type) {
case string:
splt := strings.SplitN(src, "e", 2)
if len(splt) == 1 {
return dst.SetFromDecimal(src)
}
if err := dst.SetFromDecimal(splt[0]); err != nil {
return err
}
if splt[1] == "0" {
return nil
}
exp := new(Int)
if err := exp.SetFromDecimal(splt[1]); err != nil {
return err
}
if !exp.IsUint64() || exp.Uint64() > uint64(len(twoPow256Sub1)) {
return ErrBig256Range
}
exp.Exp(NewInt(10), exp)
_, overflow := dst.MulOverflow(dst, exp)
if overflow {
return ErrBig256Range
}
return nil
return dst.scanScientificFromString(src)
case []byte:
return dst.SetFromDecimal(string(src))
return dst.scanScientificFromString(string(src))
}
return errors.New("unsupported type")
}

func (dst *Int) scanScientificFromString(src string) error {
if len(src) == 0 {
dst.Clear()
return nil
}
return fmt.Errorf("cannot scan %T", src)
idx := strings.IndexByte(src, 'e')
if idx == -1 {
return dst.SetFromDecimal(src)
}
if err := dst.SetFromDecimal(src[:idx]); err != nil {
return err
}
if src[(idx+1):] == "0" {
return nil
}
exp := new(Int)
if err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {
return err
}
if exp.GtUint64(77) { // 10**78 is larger than 2**256
return ErrBig256Range
}
exp.Exp(NewInt(10), exp)
if _, overflow := dst.MulOverflow(dst, exp); overflow {
return ErrBig256Range
}
return nil
}

// Value implements the database/sql/driver Valuer interface.
Expand Down
58 changes: 58 additions & 0 deletions conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,34 @@ func TestScanScientific(t *testing.T) {
exp *Int
err string
}{
{
in: "",
exp: new(Int),
},
{
in: "e30",
err: "EOF",
},
{
in: "30e",
err: "EOF",
},
{
in: twoPow256Sub1 + "e",
err: "EOF",
},
{
in: "14e30",
exp: new(Int).Mul(NewInt(14), new(Int).Exp(NewInt(10), NewInt(30))),
},
{ // 0xdd15fe86affad800000000000000000000000000000000000000000000000000
in: "1e77",
exp: new(Int).Mul(NewInt(1), new(Int).Exp(NewInt(10), NewInt(77))),
},
{ // 0x8a2dbf142dfcc8000000000000000000000000000000000000000000000000000
in: "1e78",
err: ErrBig256Range.Error(),
},
{
in: "1455522523e31",
exp: new(Int).Mul(NewInt(1455522523), new(Int).Exp(NewInt(10), NewInt(31))),
Expand Down Expand Up @@ -169,6 +193,40 @@ func TestToBig(t *testing.T) {
}
}

func BenchmarkScanScientific(b *testing.B) {
intsub1 := new(Int)
_ = intsub1.fromDecimal(twoPow256Sub1)
cases := []struct {
in string
exp *Int
err string
}{
{
in: "14e30",
exp: new(Int).Mul(NewInt(14), new(Int).Exp(NewInt(10), NewInt(30))),
},
{
in: "1455522523e31",
exp: new(Int).Mul(NewInt(1455522523), new(Int).Exp(NewInt(10), NewInt(31))),
},
{
in: twoPow256Sub1 + "e0",
exp: intsub1,
},
{
in: "1e00000000000000000",
exp: NewInt(1),
},
}
i := new(Int)
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
for _, v := range cases {
_ = i.Scan(v.in)
}
}
}

func benchSetFromBig(bench *testing.B, b *big.Int) Int {
var f Int
for i := 0; i < bench.N; i++ {
Expand Down

0 comments on commit c385c61

Please sign in to comment.