From 0cada0bdfec099cd2ce0c9f2fb1334289a1a598b Mon Sep 17 00:00:00 2001 From: nknordeen Date: Sat, 9 Jan 2021 21:15:12 -0700 Subject: [PATCH] optimize NewFromString a bit (#198) * optimize NewFromString a bit * add benchmark for large number Co-authored-by: Nicholas Nordeen --- decimal.go | 44 ++++++++++++++++++++++++++++++++----------- decimal_bench_test.go | 41 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/decimal.go b/decimal.go index 75f845e..22f49e5 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 b505eb0..fd3ddb6 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,43 @@ 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) + } + } + } +} + +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++ { + for _, p := range prices { + d, err := NewFromString(p) + if err != nil { + b.Log(d) + b.Error(err) + } + } + } +} \ No newline at end of file