From 3cb6b4017859e07c761dd6eb83af8e164bc20dc2 Mon Sep 17 00:00:00 2001 From: Aaron Janeiro Stone Date: Tue, 21 Jan 2025 16:50:28 -0500 Subject: [PATCH] feat: Add public NewFromDecimal which outperforms decimal -> string -> alpacadecimal (#13) * add decimal -> alpacadecimal conversion (21/01/2025 signed) * fix: adjust field alignment (#12) * refactor: use convention as @neal suggested (21/01/2025 signed) --- benchmark_test.go | 35 +++++++++++++++++++++++++++++++++++ decimal.go | 31 ++++++++++++++++++++++++++++--- decimal_test.go | 16 ++++++++++++++++ 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/benchmark_test.go b/benchmark_test.go index 97eb0e5..35394b7 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -360,3 +360,38 @@ func BenchmarkRound(b *testing.B) { _ = result }) } + +func BenchmarkNewFromDecimal(b *testing.B) { + b.Run("alpacadecimal.Decimal.NewFromDecimal", func(b *testing.B) { + d := decimal.New(123, -12) + + var result alpacadecimal.Decimal + + b.ResetTimer() + for n := 0; n < b.N; n++ { + result = alpacadecimal.NewFromDecimal(d) + } + _ = result + }) + + b.Run("alpacadecimal.Decimal.RequireFromString", func(b *testing.B) { + d := decimal.New(123, -12) + + var result alpacadecimal.Decimal + + b.ResetTimer() + for n := 0; n < b.N; n++ { + result = alpacadecimal.RequireFromString(d.String()) + } + _ = result + }) + + b.Run("alpacadecimal.Decimal.New", func(b *testing.B) { + var result alpacadecimal.Decimal + for n := 0; n < b.N; n++ { + result = alpacadecimal.New(123, -12) + } + _ = result + }) + +} diff --git a/decimal.go b/decimal.go index 1ae773c..818c66b 100644 --- a/decimal.go +++ b/decimal.go @@ -126,20 +126,28 @@ func Min(first Decimal, rest ...Decimal) Decimal { // optimized: // New returns a new fixed-point decimal, value * 10 ^ exp. func New(value int64, exp int32) Decimal { + d, ok := tryOptNew(value, exp) + if ok { + return d + } + return newFromDecimal(decimal.New(value, exp)) +} + +func tryOptNew(value int64, exp int32) (Decimal, bool) { if exp >= -12 { if exp <= 0 { s := pow10Table[-exp] if value >= minInt*s && value <= maxInt*s { - return Decimal{fixed: value * pow10Table[precision+exp]} + return Decimal{fixed: value * pow10Table[precision+exp]}, true } } else if exp <= 6 { // when exp > 6, it would be greater than maxInt s := pow10Table[exp] if value >= minInt/s && value <= maxInt/s { - return Decimal{fixed: value * pow10Table[precision+exp]} + return Decimal{fixed: value * pow10Table[precision+exp]}, true } } } - return newFromDecimal(decimal.New(value, exp)) + return Decimal{}, false } // fallback: @@ -1100,6 +1108,23 @@ func (d NullDecimal) Value() (driver.Value, error) { return d.Decimal.Value() } +// optimized: +// Create a new alpacadecimal.Decimal from a decimal.Decimal. +// Attempts to set the fixed value if possible. +func NewFromDecimal(d decimal.Decimal) Decimal { + co := d.Coefficient() + if !co.IsInt64() { + return newFromDecimal(d) // fallback + } + value := co.Int64() + exp := d.Exponent() + res, ok := tryOptNew(value, exp) + if ok { + return res + } + return newFromDecimal(d) +} + // internal implementation func newFromDecimal(d decimal.Decimal) Decimal { return Decimal{fallback: &d} diff --git a/decimal_test.go b/decimal_test.go index 5061c5b..4442cf1 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -198,6 +198,22 @@ func TestDecimal(t *testing.T) { require.Equal(t, x.String(), y.String()) }) + t.Run("NewFromDecimal", func(t *testing.T) { + // first, with optimized decimal + x := alpacadecimal.NewFromDecimal(decimal.New(123, -2)) + y := alpacadecimal.New(123, -2) + shouldEqual(t, x, y) + + // the prior means of conversion from decimal commonly used + y = alpacadecimal.RequireFromString(decimal.New(123, -2).String()) + shouldEqual(t, x, y) + + // now, with out of optimization range decimal + x = alpacadecimal.NewFromDecimal(decimal.New(123, -13)) + y = alpacadecimal.New(123, -13) + shouldEqual(t, x, y) + }) + t.Run("NewFromInt", func(t *testing.T) { x := alpacadecimal.NewFromInt(123) y, err := alpacadecimal.NewFromString("123")