diff --git a/decimal.go b/decimal.go index 0224292..f9ef1b8 100644 --- a/decimal.go +++ b/decimal.go @@ -956,14 +956,31 @@ func (d Decimal) Ln(precision int32) (Decimal, error) { } // NumDigits returns the number of digits of the decimal coefficient (d.Value) -// Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part func (d Decimal) NumDigits() int { - d.ensureInitialized() - // Note(mwoss): It can be optimized, unnecessary cast of big.Int to string - if d.IsNegative() { - return len(d.value.String()) - 1 + if d.value == nil { + return 1 } - return len(d.value.String()) + + if d.value.IsUint64() { + u64 := d.value.Uint64() + if u64 < (1 << 53) { + if u64 == 0 { + return 1 + } + return int(math.Log10(float64(u64))) + 1 + } + } else if d.value.IsInt64() { + i64 := d.value.Int64() + if i64 > -(1 << 53) { + return int(math.Log10(float64(-i64))) + 1 + } + } + + abs := new(big.Int).Abs(d.value) + // lg10 may be off by 1, need to verify + lg10 := int(float64(abs.BitLen()) / math.Log2(10)) + check := big.NewInt(int64(lg10)) + return lg10 + abs.Cmp(check.Exp(tenInt, check, nil)) } // IsInteger returns true when decimal can be represented as an integer value, otherwise, it returns false. diff --git a/decimal_bench_test.go b/decimal_bench_test.go index 269a9f6..fdbee24 100644 --- a/decimal_bench_test.go +++ b/decimal_bench_test.go @@ -120,6 +120,34 @@ func BenchmarkDecimal_RoundCash_Five(b *testing.B) { } } +func numDigits(b *testing.B, want int, val Decimal) { + b.Helper() + for i := 0; i < b.N; i++ { + if have := val.NumDigits(); have != want { + b.Fatalf("\nHave: %q\nWant: %q", have, want) + } + } +} + +func BenchmarkDecimal_NumDigits10(b *testing.B) { + numDigits(b, 10, New(3478512345, -3)) +} + +func BenchmarkDecimal_NumDigits100(b *testing.B) { + s := make([]byte, 102) + for i := range s { + s[i] = byte('0' + i%10) + } + s[0] = '-' + s[100] = '.' + d, err := NewFromString(string(s)) + if err != nil { + b.Log(d) + b.Error(err) + } + numDigits(b, 100, d) +} + func Benchmark_Cmp(b *testing.B) { decimals := DecimalSlice([]Decimal{}) for i := 0; i < 1000000; i++ { @@ -131,7 +159,7 @@ func Benchmark_Cmp(b *testing.B) { } } -func Benchmark_decimal_Decimal_Add_different_precision(b *testing.B) { +func BenchmarkDecimal_Add_different_precision(b *testing.B) { d1 := NewFromFloat(1000.123) d2 := NewFromFloat(500).Mul(NewFromFloat(0.12)) @@ -142,7 +170,7 @@ func Benchmark_decimal_Decimal_Add_different_precision(b *testing.B) { } } -func Benchmark_decimal_Decimal_Sub_different_precision(b *testing.B) { +func BenchmarkDecimal_Sub_different_precision(b *testing.B) { d1 := NewFromFloat(1000.123) d2 := NewFromFloat(500).Mul(NewFromFloat(0.12)) @@ -153,7 +181,7 @@ func Benchmark_decimal_Decimal_Sub_different_precision(b *testing.B) { } } -func Benchmark_decimal_Decimal_Add_same_precision(b *testing.B) { +func BenchmarkDecimal_Add_same_precision(b *testing.B) { d1 := NewFromFloat(1000.123) d2 := NewFromFloat(500.123) @@ -164,7 +192,7 @@ func Benchmark_decimal_Decimal_Add_same_precision(b *testing.B) { } } -func Benchmark_decimal_Decimal_Sub_same_precision(b *testing.B) { +func BenchmarkDecimal_Sub_same_precision(b *testing.B) { d1 := NewFromFloat(1000.123) d2 := NewFromFloat(500.123)