diff --git a/decimal.go b/decimal.go index eed845f..941aae4 100644 --- a/decimal.go +++ b/decimal.go @@ -129,11 +129,10 @@ func NewFromBigInt(value *big.Int, exp int32) Decimal { // // Example: // -// d1 := NewFromBigRat(big.NewRat(0, 1), 0) // output: "0" -// d2 := NewFromBigRat(big.NewRat(4, 5), 1) // output: "0.8" -// d3 := NewFromBigRat(big.NewRat(1000, 3), 3) // output: "333.333" -// d4 := NewFromBigRat(big.NewRat(2, 7), 4) // output: "0.2857" -// +// d1 := NewFromBigRat(big.NewRat(0, 1), 0) // output: "0" +// d2 := NewFromBigRat(big.NewRat(4, 5), 1) // output: "0.8" +// d3 := NewFromBigRat(big.NewRat(1000, 3), 3) // output: "333.333" +// d4 := NewFromBigRat(big.NewRat(2, 7), 4) // output: "0.2857" func NewFromBigRat(value *big.Rat, precision int32) Decimal { return Decimal{ value: new(big.Int).Set(value.Num()), @@ -946,14 +945,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)