Skip to content

Commit c385c61

Browse files
elee1766holiman
andauthored
conversion: optimize Scan() scientific scanning (#127)
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]>
1 parent 146987f commit c385c61

File tree

3 files changed

+96
-25
lines changed

3 files changed

+96
-25
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
/.idea
2+
3+
4+
# go output files
5+
*.test
6+
*.out
7+
*.pb.gz

conversion.go

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -615,33 +615,40 @@ func (dst *Int) Scan(src interface{}) error {
615615
}
616616
switch src := src.(type) {
617617
case string:
618-
splt := strings.SplitN(src, "e", 2)
619-
if len(splt) == 1 {
620-
return dst.SetFromDecimal(src)
621-
}
622-
if err := dst.SetFromDecimal(splt[0]); err != nil {
623-
return err
624-
}
625-
if splt[1] == "0" {
626-
return nil
627-
}
628-
exp := new(Int)
629-
if err := exp.SetFromDecimal(splt[1]); err != nil {
630-
return err
631-
}
632-
if !exp.IsUint64() || exp.Uint64() > uint64(len(twoPow256Sub1)) {
633-
return ErrBig256Range
634-
}
635-
exp.Exp(NewInt(10), exp)
636-
_, overflow := dst.MulOverflow(dst, exp)
637-
if overflow {
638-
return ErrBig256Range
639-
}
640-
return nil
618+
return dst.scanScientificFromString(src)
641619
case []byte:
642-
return dst.SetFromDecimal(string(src))
620+
return dst.scanScientificFromString(string(src))
621+
}
622+
return errors.New("unsupported type")
623+
}
624+
625+
func (dst *Int) scanScientificFromString(src string) error {
626+
if len(src) == 0 {
627+
dst.Clear()
628+
return nil
643629
}
644-
return fmt.Errorf("cannot scan %T", src)
630+
idx := strings.IndexByte(src, 'e')
631+
if idx == -1 {
632+
return dst.SetFromDecimal(src)
633+
}
634+
if err := dst.SetFromDecimal(src[:idx]); err != nil {
635+
return err
636+
}
637+
if src[(idx+1):] == "0" {
638+
return nil
639+
}
640+
exp := new(Int)
641+
if err := exp.SetFromDecimal(src[(idx + 1):]); err != nil {
642+
return err
643+
}
644+
if exp.GtUint64(77) { // 10**78 is larger than 2**256
645+
return ErrBig256Range
646+
}
647+
exp.Exp(NewInt(10), exp)
648+
if _, overflow := dst.MulOverflow(dst, exp); overflow {
649+
return ErrBig256Range
650+
}
651+
return nil
645652
}
646653

647654
// Value implements the database/sql/driver Valuer interface.

conversion_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,34 @@ func TestScanScientific(t *testing.T) {
8484
exp *Int
8585
err string
8686
}{
87+
{
88+
in: "",
89+
exp: new(Int),
90+
},
91+
{
92+
in: "e30",
93+
err: "EOF",
94+
},
95+
{
96+
in: "30e",
97+
err: "EOF",
98+
},
99+
{
100+
in: twoPow256Sub1 + "e",
101+
err: "EOF",
102+
},
87103
{
88104
in: "14e30",
89105
exp: new(Int).Mul(NewInt(14), new(Int).Exp(NewInt(10), NewInt(30))),
90106
},
107+
{ // 0xdd15fe86affad800000000000000000000000000000000000000000000000000
108+
in: "1e77",
109+
exp: new(Int).Mul(NewInt(1), new(Int).Exp(NewInt(10), NewInt(77))),
110+
},
111+
{ // 0x8a2dbf142dfcc8000000000000000000000000000000000000000000000000000
112+
in: "1e78",
113+
err: ErrBig256Range.Error(),
114+
},
91115
{
92116
in: "1455522523e31",
93117
exp: new(Int).Mul(NewInt(1455522523), new(Int).Exp(NewInt(10), NewInt(31))),
@@ -169,6 +193,40 @@ func TestToBig(t *testing.T) {
169193
}
170194
}
171195

196+
func BenchmarkScanScientific(b *testing.B) {
197+
intsub1 := new(Int)
198+
_ = intsub1.fromDecimal(twoPow256Sub1)
199+
cases := []struct {
200+
in string
201+
exp *Int
202+
err string
203+
}{
204+
{
205+
in: "14e30",
206+
exp: new(Int).Mul(NewInt(14), new(Int).Exp(NewInt(10), NewInt(30))),
207+
},
208+
{
209+
in: "1455522523e31",
210+
exp: new(Int).Mul(NewInt(1455522523), new(Int).Exp(NewInt(10), NewInt(31))),
211+
},
212+
{
213+
in: twoPow256Sub1 + "e0",
214+
exp: intsub1,
215+
},
216+
{
217+
in: "1e00000000000000000",
218+
exp: NewInt(1),
219+
},
220+
}
221+
i := new(Int)
222+
b.ResetTimer()
223+
for idx := 0; idx < b.N; idx++ {
224+
for _, v := range cases {
225+
_ = i.Scan(v.in)
226+
}
227+
}
228+
}
229+
172230
func benchSetFromBig(bench *testing.B, b *big.Int) Int {
173231
var f Int
174232
for i := 0; i < bench.N; i++ {

0 commit comments

Comments
 (0)