diff --git a/decimal.go b/decimal.go index c182081..c67aaca 100644 --- a/decimal.go +++ b/decimal.go @@ -1216,14 +1216,33 @@ 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 + } + + if d.value.IsInt64() { + i64 := d.value.Int64() + // restrict fast path to integers with exact conversion to float64 + if i64 <= (1<<53) && i64 >= -(1<<53) { + if i64 == 0 { + return 1 + } + return int(math.Log10(math.Abs(float64(i64)))) + 1 + } + } + + estimatedNumDigits := int(float64(d.value.BitLen()) / math.Log2(10)) + + // estimatedNumDigits (lg10) may be off by 1, need to verify + digitsBigInt := big.NewInt(int64(estimatedNumDigits)) + errorCorrectionUnit := digitsBigInt.Exp(tenInt, digitsBigInt, nil) + + if d.value.CmpAbs(errorCorrectionUnit) >= 0 { + return estimatedNumDigits + 1 } - return len(d.value.String()) + + return estimatedNumDigits } // 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 b1978bc..34e038f 100644 --- a/decimal_bench_test.go +++ b/decimal_bench_test.go @@ -121,6 +121,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: %d\nWant: %d", 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++ { @@ -132,7 +160,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)) @@ -143,7 +171,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)) @@ -154,7 +182,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) @@ -165,7 +193,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)