mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 20:40:48 +01:00
Remove unnecessary decimal rescaling - memory usage optimization (#160)
* Remove unnecessary decimal rescaling * Move benchmarks to separate file, add new benchmark tests
This commit is contained in:
parent
96defcb63c
commit
408a2507e1
3 changed files with 214 additions and 143 deletions
40
decimal.go
40
decimal.go
|
@ -390,6 +390,14 @@ func NewFromFloatWithExponent(value float64, exp int32) Decimal {
|
|||
//
|
||||
func (d Decimal) rescale(exp int32) Decimal {
|
||||
d.ensureInitialized()
|
||||
|
||||
if d.exp == exp {
|
||||
return Decimal{
|
||||
new(big.Int).Set(d.value),
|
||||
d.exp,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(vadim): must convert exps to float64 before - to prevent overflow
|
||||
diff := math.Abs(float64(exp) - float64(d.exp))
|
||||
value := new(big.Int).Set(d.value)
|
||||
|
@ -419,27 +427,23 @@ func (d Decimal) Abs() Decimal {
|
|||
|
||||
// Add returns d + d2.
|
||||
func (d Decimal) Add(d2 Decimal) Decimal {
|
||||
baseScale := min(d.exp, d2.exp)
|
||||
rd := d.rescale(baseScale)
|
||||
rd2 := d2.rescale(baseScale)
|
||||
rd, rd2 := RescalePair(d, d2)
|
||||
|
||||
d3Value := new(big.Int).Add(rd.value, rd2.value)
|
||||
return Decimal{
|
||||
value: d3Value,
|
||||
exp: baseScale,
|
||||
exp: rd.exp,
|
||||
}
|
||||
}
|
||||
|
||||
// Sub returns d - d2.
|
||||
func (d Decimal) Sub(d2 Decimal) Decimal {
|
||||
baseScale := min(d.exp, d2.exp)
|
||||
rd := d.rescale(baseScale)
|
||||
rd2 := d2.rescale(baseScale)
|
||||
rd, rd2 := RescalePair(d, d2)
|
||||
|
||||
d3Value := new(big.Int).Sub(rd.value, rd2.value)
|
||||
return Decimal{
|
||||
value: d3Value,
|
||||
exp: baseScale,
|
||||
exp: rd.exp,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,9 +604,7 @@ func (d Decimal) Cmp(d2 Decimal) int {
|
|||
return d.value.Cmp(d2.value)
|
||||
}
|
||||
|
||||
baseExp := min(d.exp, d2.exp)
|
||||
rd := d.rescale(baseExp)
|
||||
rd2 := d2.rescale(baseExp)
|
||||
rd, rd2 := RescalePair(d, d2)
|
||||
|
||||
return rd.value.Cmp(rd2.value)
|
||||
}
|
||||
|
@ -1172,6 +1174,22 @@ func Avg(first Decimal, rest ...Decimal) Decimal {
|
|||
return sum.Div(count)
|
||||
}
|
||||
|
||||
// Rescale two decimals to common exponential value (minimal exp of both decimals)
|
||||
func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) {
|
||||
d1.ensureInitialized()
|
||||
d2.ensureInitialized()
|
||||
|
||||
if d1.exp == d2.exp {
|
||||
return d1, d2
|
||||
}
|
||||
|
||||
baseScale := min(d1.exp, d2.exp)
|
||||
if baseScale != d1.exp {
|
||||
return d1.rescale(baseScale), d2
|
||||
}
|
||||
return d1, d2.rescale(baseScale)
|
||||
}
|
||||
|
||||
func min(x, y int32) int32 {
|
||||
if x >= y {
|
||||
return y
|
||||
|
|
185
decimal_bench_test.go
Normal file
185
decimal_bench_test.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package decimal
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DecimalSlice []Decimal
|
||||
|
||||
func (p DecimalSlice) Len() int { return len(p) }
|
||||
func (p DecimalSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p DecimalSlice) Less(i, j int) bool { return p[i].Cmp(p[j]) < 0 }
|
||||
|
||||
func BenchmarkNewFromFloatWithExponent(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
in := rng.NormFloat64() * 10e20
|
||||
_ = NewFromFloatWithExponent(in, math.MinInt32)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewFromFloat(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewFromFloat(in[i])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewFromStringFloat(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
in := strconv.FormatFloat(in[i], 'f', -1, 64)
|
||||
_, _ = NewFromString(in)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_FloorFast(b *testing.B) {
|
||||
input := New(200, 2)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
input.Floor()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_FloorRegular(b *testing.B) {
|
||||
input := New(200, -2)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
input.Floor()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DivideOriginal(b *testing.B) {
|
||||
tcs := createDivTestCases()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range tcs {
|
||||
d := tc.d
|
||||
if sign(tc.d2) == 0 {
|
||||
continue
|
||||
}
|
||||
d2 := tc.d2
|
||||
prec := tc.prec
|
||||
a := d.DivOld(d2, int(prec))
|
||||
if sign(a) > 2 {
|
||||
panic("dummy panic")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DivideNew(b *testing.B) {
|
||||
tcs := createDivTestCases()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range tcs {
|
||||
d := tc.d
|
||||
if sign(tc.d2) == 0 {
|
||||
continue
|
||||
}
|
||||
d2 := tc.d2
|
||||
prec := tc.prec
|
||||
a := d.DivRound(d2, prec)
|
||||
if sign(a) > 2 {
|
||||
panic("dummy panic")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecimal_RoundCash_Five(b *testing.B) {
|
||||
const want = "3.50"
|
||||
for i := 0; i < b.N; i++ {
|
||||
val := New(3478, -3)
|
||||
if have := val.StringFixedCash(5); have != want {
|
||||
b.Fatalf("\nHave: %q\nWant: %q", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecimal_RoundCash_Fifteen(b *testing.B) {
|
||||
const want = "6.30"
|
||||
for i := 0; i < b.N; i++ {
|
||||
val := New(635, -2)
|
||||
if have := val.StringFixedCash(15); have != want {
|
||||
b.Fatalf("\nHave: %q\nWant: %q", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Cmp(b *testing.B) {
|
||||
decimals := DecimalSlice([]Decimal{})
|
||||
for i := 0; i < 1000000; i++ {
|
||||
decimals = append(decimals, New(int64(i), 0))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sort.Sort(decimals)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_decimal_Decimal_Add_different_precision(b *testing.B) {
|
||||
d1 := NewFromFloat(1000.123)
|
||||
d2 := NewFromFloat(500).Mul(NewFromFloat(0.12))
|
||||
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Add(d2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_decimal_Decimal_Sub_different_precision(b *testing.B) {
|
||||
d1 := NewFromFloat(1000.123)
|
||||
d2 := NewFromFloat(500).Mul(NewFromFloat(0.12))
|
||||
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Sub(d2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_decimal_Decimal_Add_same_precision(b *testing.B) {
|
||||
d1 := NewFromFloat(1000.123)
|
||||
d2 := NewFromFloat(500.123)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Add(d2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_decimal_Decimal_Sub_same_precision(b *testing.B) {
|
||||
d1 := NewFromFloat(1000.123)
|
||||
d2 := NewFromFloat(500.123)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Add(d2)
|
||||
}
|
||||
}
|
132
decimal_test.go
132
decimal_test.go
|
@ -9,7 +9,6 @@ import (
|
|||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -201,47 +200,6 @@ func TestNewFromFloat32Quick(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewFromFloatWithExponent(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
in := rng.NormFloat64() * 10e20
|
||||
_ = NewFromFloatWithExponent(in, math.MinInt32)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewFromFloat(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = NewFromFloat(in[i])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewFromStringFloat(b *testing.B) {
|
||||
rng := rand.New(rand.NewSource(0xdead1337))
|
||||
in := make([]float64, b.N)
|
||||
for i := range in {
|
||||
in[i] = rng.NormFloat64() * 10e20
|
||||
}
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
in := strconv.FormatFloat(in[i], 'f', -1, 64)
|
||||
_, _ = NewFromString(in)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFromString(t *testing.T) {
|
||||
for _, x := range testTable {
|
||||
s := x.short
|
||||
|
@ -862,22 +820,6 @@ func TestDecimal_Floor(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Benchmark_FloorFast(b *testing.B) {
|
||||
input := New(200, 2)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
input.Floor()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_FloorRegular(b *testing.B) {
|
||||
input := New(200, -2)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
input.Floor()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecimal_Ceil(t *testing.T) {
|
||||
assertCeil := func(input, expected Decimal) {
|
||||
got := input.Ceil()
|
||||
|
@ -1525,44 +1467,6 @@ func (d Decimal) DivOld(d2 Decimal, prec int) Decimal {
|
|||
return ret
|
||||
}
|
||||
|
||||
func Benchmark_DivideOriginal(b *testing.B) {
|
||||
tcs := createDivTestCases()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range tcs {
|
||||
d := tc.d
|
||||
if sign(tc.d2) == 0 {
|
||||
continue
|
||||
}
|
||||
d2 := tc.d2
|
||||
prec := tc.prec
|
||||
a := d.DivOld(d2, int(prec))
|
||||
if sign(a) > 2 {
|
||||
panic("dummy panic")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_DivideNew(b *testing.B) {
|
||||
tcs := createDivTestCases()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, tc := range tcs {
|
||||
d := tc.d
|
||||
if sign(tc.d2) == 0 {
|
||||
continue
|
||||
}
|
||||
d2 := tc.d2
|
||||
prec := tc.prec
|
||||
a := d.DivRound(d2, prec)
|
||||
if sign(a) > 2 {
|
||||
panic("dummy panic")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sign(d Decimal) int {
|
||||
return d.value.Sign()
|
||||
}
|
||||
|
@ -1719,26 +1623,6 @@ func TestDecimal_RoundCash_Panic(t *testing.T) {
|
|||
d.RoundCash(231)
|
||||
}
|
||||
|
||||
func BenchmarkDecimal_RoundCash_Five(b *testing.B) {
|
||||
const want = "3.50"
|
||||
for i := 0; i < b.N; i++ {
|
||||
val := New(3478, -3)
|
||||
if have := val.StringFixedCash(5); have != want {
|
||||
b.Fatalf("\nHave: %q\nWant: %q", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecimal_RoundCash_Fifteen(b *testing.B) {
|
||||
const want = "6.30"
|
||||
for i := 0; i < b.N; i++ {
|
||||
val := New(635, -2)
|
||||
if have := val.StringFixedCash(15); have != want {
|
||||
b.Fatalf("\nHave: %q\nWant: %q", have, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecimal_Mod(t *testing.T) {
|
||||
type Inp struct {
|
||||
a string
|
||||
|
@ -2193,22 +2077,6 @@ func TestDecimal_Coefficient(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type DecimalSlice []Decimal
|
||||
|
||||
func (p DecimalSlice) Len() int { return len(p) }
|
||||
func (p DecimalSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
func (p DecimalSlice) Less(i, j int) bool { return p[i].Cmp(p[j]) < 0 }
|
||||
func Benchmark_Cmp(b *testing.B) {
|
||||
decimals := DecimalSlice([]Decimal{})
|
||||
for i := 0; i < 1000000; i++ {
|
||||
decimals = append(decimals, New(int64(i), 0))
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sort.Sort(decimals)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNullDecimal_Scan(t *testing.T) {
|
||||
// test the Scan method that implements the
|
||||
// sql.Scanner interface
|
||||
|
|
Loading…
Reference in a new issue