Compare commits

..

2 commits

Author SHA1 Message Date
Philip Dubé
dcf2a306fd
Merge 6e15183b56 into 78289cc844 2024-04-03 09:01:04 +08:00
Philip Dubé
6e15183b56 Optimize NumDigits
Dividing BitLen by math.Log2(10) is what math/big does underneath

Not including the Int64/Uint64 check makes this slightly slower than old method

Included 2 benchmarks, for 10 digit numbers & 100 digit numbers:

-- before
> go test -bench=NumDigit -run=NumDigit
goos: linux
goarch: amd64
pkg: github.com/shopspring/decimal
cpu: AMD Ryzen 7 7840U w/ Radeon  780M Graphics
BenchmarkDecimal_NumDigits10-16     	18317293	        63.87 ns/op
BenchmarkDecimal_NumDigits100-16    	 3645015	       329.6 ns/op

-- after
...
BenchmarkDecimal_NumDigits10-16     	143781325	         8.488 ns/op
BenchmarkDecimal_NumDigits100-16    	 5931247	       207.4 ns/op
2024-03-28 19:49:01 +00:00

View file

@ -1229,27 +1229,26 @@ func (d Decimal) NumDigits() int {
return 1 return 1
} }
if d.value.IsInt64() { if d.value.IsUint64() {
i64 := d.value.Int64() u64 := d.value.Uint64()
if i64 <= (1<<53) && i64 >= -(1<<53) { if u64 < (1 << 53) {
if i64 == 0 { if u64 == 0 {
return 1 return 1
} }
return int(math.Log10(math.Abs(float64(i64)))) + 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
} }
} }
estimatedNumDigits := int(float64(d.value.BitLen()) / math.Log2(10)) abs := new(big.Int).Abs(d.value)
// lg10 may be off by 1, need to verify
// estimatedNumDigits (lg10) may be off by 1, need to verify lg10 := int(float64(abs.BitLen()) / math.Log2(10))
digitsBigInt := big.NewInt(int64(estimatedNumDigits)) check := big.NewInt(int64(lg10))
errorCorrectionUnit := digitsBigInt.Exp(tenInt, digitsBigInt, nil) return lg10 + abs.Cmp(check.Exp(tenInt, check, nil))
if d.value.CmpAbs(errorCorrectionUnit) >= 0 {
return estimatedNumDigits + 1
}
return estimatedNumDigits
} }
// IsInteger returns true when decimal can be represented as an integer value, otherwise, it returns false. // IsInteger returns true when decimal can be represented as an integer value, otherwise, it returns false.
@ -1802,18 +1801,19 @@ func (d *Decimal) UnmarshalBinary(data []byte) error {
// MarshalBinary implements the encoding.BinaryMarshaler interface. // MarshalBinary implements the encoding.BinaryMarshaler interface.
func (d Decimal) MarshalBinary() (data []byte, err error) { func (d Decimal) MarshalBinary() (data []byte, err error) {
// exp is written first, but encode value first to know output size // Write the exponent first since it's a fixed size
var valueData []byte v1 := make([]byte, 4)
if valueData, err = d.value.GobEncode(); err != nil { binary.BigEndian.PutUint32(v1, uint32(d.exp))
return nil, err
// Add the value
var v2 []byte
if v2, err = d.value.GobEncode(); err != nil {
return
} }
// Write the exponent in front, since it's a fixed size
expData := make([]byte, 4, len(valueData)+4)
binary.BigEndian.PutUint32(expData, uint32(d.exp))
// Return the byte array // Return the byte array
return append(expData, valueData...), nil data = append(v1, v2...)
return
} }
// Scan implements the sql.Scanner interface for database deserialization. // Scan implements the sql.Scanner interface for database deserialization.