From 854b3942a7cf1ed3776215eaef32f500c35a43bd Mon Sep 17 00:00:00 2001 From: Nicholas Nordeen Date: Thu, 7 Jan 2021 14:43:26 -0700 Subject: [PATCH 1/2] optimize NewFromString a bit --- decimal.go | 44 ++++++++++++++++++++++++++++++++----------- decimal_bench_test.go | 21 +++++++++++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/decimal.go b/decimal.go index 75f845ef..22f49e58 100644 --- a/decimal.go +++ b/decimal.go @@ -147,23 +147,45 @@ func NewFromString(value string) (Decimal, error) { exp = expInt } - parts := strings.Split(value, ".") - if len(parts) == 1 { + pIndex := -1 + vLen := len(value) + for i := 0; i < vLen; i++ { + if value[i] == '.' { + if pIndex > -1 { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) + } + pIndex = i + } + } + + if pIndex == -1 { // There is no decimal point, we can just parse the original string as // an int intString = value - } else if len(parts) == 2 { - intString = parts[0] + parts[1] - expInt := -len(parts[1]) - exp += int64(expInt) } else { - return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) + if pIndex+1 < vLen { + intString = value[:pIndex] + value[pIndex+1:] + } else { + intString = value[:pIndex] + } + expInt := -len(value[pIndex+1:]) + exp += int64(expInt) } - dValue := new(big.Int) - _, ok := dValue.SetString(intString, 10) - if !ok { - return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) + var dValue *big.Int + // strconv.ParseInt is faster than new(big.Int).SetString so this is just a shortcut for strings we know won't overflow + if len(intString) <= 18 { + parsed64, err := strconv.ParseInt(intString, 10, 64) + if err != nil { + return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) + } + dValue = big.NewInt(parsed64) + } else { + dValue = new(big.Int) + _, ok := dValue.SetString(intString, 10) + if !ok { + return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) + } } if exp < math.MinInt32 || exp > math.MaxInt32 { diff --git a/decimal_bench_test.go b/decimal_bench_test.go index b505eb08..4808b5a2 100644 --- a/decimal_bench_test.go +++ b/decimal_bench_test.go @@ -1,6 +1,7 @@ package decimal import ( + "fmt" "math" "math/rand" "sort" @@ -183,3 +184,23 @@ func BenchmarkDecimal_IsInteger(b *testing.B) { d.IsInteger() } } + +func BenchmarkDecimal_NewFromString(b *testing.B) { + count := 72 + prices := make([]string, 0, count) + for i := 1; i <= count; i++ { + prices = append(prices, fmt.Sprintf("%d.%d", i*100, i)) + } + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, p := range prices { + d, err := NewFromString(p) + if err != nil { + b.Log(d) + b.Error(err) + } + } + } +} \ No newline at end of file From 6bedb14d1409851f39fec4f3d57efa22877c10b2 Mon Sep 17 00:00:00 2001 From: Nicholas Nordeen Date: Thu, 7 Jan 2021 14:51:40 -0700 Subject: [PATCH 2/2] add benchmark for large number --- decimal_bench_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/decimal_bench_test.go b/decimal_bench_test.go index 4808b5a2..fd3ddb6f 100644 --- a/decimal_bench_test.go +++ b/decimal_bench_test.go @@ -192,6 +192,26 @@ func BenchmarkDecimal_NewFromString(b *testing.B) { prices = append(prices, fmt.Sprintf("%d.%d", i*100, i)) } + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + for _, p := range prices { + d, err := NewFromString(p) + if err != nil { + b.Log(d) + b.Error(err) + } + } + } +} + +func BenchmarkDecimal_NewFromString_large_number(b *testing.B) { + count := 72 + prices := make([]string, 0, count) + for i := 1; i <= count; i++ { + prices = append(prices, "9323372036854775807.9223372036854775807") + } + b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ {