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 {
|
func (d Decimal) rescale(exp int32) Decimal {
|
||||||
d.ensureInitialized()
|
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
|
// NOTE(vadim): must convert exps to float64 before - to prevent overflow
|
||||||
diff := math.Abs(float64(exp) - float64(d.exp))
|
diff := math.Abs(float64(exp) - float64(d.exp))
|
||||||
value := new(big.Int).Set(d.value)
|
value := new(big.Int).Set(d.value)
|
||||||
|
@ -419,27 +427,23 @@ func (d Decimal) Abs() Decimal {
|
||||||
|
|
||||||
// Add returns d + d2.
|
// Add returns d + d2.
|
||||||
func (d Decimal) Add(d2 Decimal) Decimal {
|
func (d Decimal) Add(d2 Decimal) Decimal {
|
||||||
baseScale := min(d.exp, d2.exp)
|
rd, rd2 := RescalePair(d, d2)
|
||||||
rd := d.rescale(baseScale)
|
|
||||||
rd2 := d2.rescale(baseScale)
|
|
||||||
|
|
||||||
d3Value := new(big.Int).Add(rd.value, rd2.value)
|
d3Value := new(big.Int).Add(rd.value, rd2.value)
|
||||||
return Decimal{
|
return Decimal{
|
||||||
value: d3Value,
|
value: d3Value,
|
||||||
exp: baseScale,
|
exp: rd.exp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub returns d - d2.
|
// Sub returns d - d2.
|
||||||
func (d Decimal) Sub(d2 Decimal) Decimal {
|
func (d Decimal) Sub(d2 Decimal) Decimal {
|
||||||
baseScale := min(d.exp, d2.exp)
|
rd, rd2 := RescalePair(d, d2)
|
||||||
rd := d.rescale(baseScale)
|
|
||||||
rd2 := d2.rescale(baseScale)
|
|
||||||
|
|
||||||
d3Value := new(big.Int).Sub(rd.value, rd2.value)
|
d3Value := new(big.Int).Sub(rd.value, rd2.value)
|
||||||
return Decimal{
|
return Decimal{
|
||||||
value: d3Value,
|
value: d3Value,
|
||||||
exp: baseScale,
|
exp: rd.exp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -600,9 +604,7 @@ func (d Decimal) Cmp(d2 Decimal) int {
|
||||||
return d.value.Cmp(d2.value)
|
return d.value.Cmp(d2.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
baseExp := min(d.exp, d2.exp)
|
rd, rd2 := RescalePair(d, d2)
|
||||||
rd := d.rescale(baseExp)
|
|
||||||
rd2 := d2.rescale(baseExp)
|
|
||||||
|
|
||||||
return rd.value.Cmp(rd2.value)
|
return rd.value.Cmp(rd2.value)
|
||||||
}
|
}
|
||||||
|
@ -1172,6 +1174,22 @@ func Avg(first Decimal, rest ...Decimal) Decimal {
|
||||||
return sum.Div(count)
|
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 {
|
func min(x, y int32) int32 {
|
||||||
if x >= y {
|
if x >= y {
|
||||||
return 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/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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) {
|
func TestNewFromString(t *testing.T) {
|
||||||
for _, x := range testTable {
|
for _, x := range testTable {
|
||||||
s := x.short
|
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) {
|
func TestDecimal_Ceil(t *testing.T) {
|
||||||
assertCeil := func(input, expected Decimal) {
|
assertCeil := func(input, expected Decimal) {
|
||||||
got := input.Ceil()
|
got := input.Ceil()
|
||||||
|
@ -1525,44 +1467,6 @@ func (d Decimal) DivOld(d2 Decimal, prec int) Decimal {
|
||||||
return ret
|
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 {
|
func sign(d Decimal) int {
|
||||||
return d.value.Sign()
|
return d.value.Sign()
|
||||||
}
|
}
|
||||||
|
@ -1719,26 +1623,6 @@ func TestDecimal_RoundCash_Panic(t *testing.T) {
|
||||||
d.RoundCash(231)
|
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) {
|
func TestDecimal_Mod(t *testing.T) {
|
||||||
type Inp struct {
|
type Inp struct {
|
||||||
a string
|
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) {
|
func TestNullDecimal_Scan(t *testing.T) {
|
||||||
// test the Scan method that implements the
|
// test the Scan method that implements the
|
||||||
// sql.Scanner interface
|
// sql.Scanner interface
|
||||||
|
|
Loading…
Reference in a new issue