Skip to content

Commit

Permalink
Allow decompose to make use of the buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
Gilthoniel committed Feb 12, 2024
1 parent cb6ebb8 commit b1a957e
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 4 deletions.
22 changes: 18 additions & 4 deletions decomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ type decomposer interface {

var _ decomposer = &Decimal{}

// Decompose returns the internal decimal state into parts.
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
// the value set and length set as appropriate.
// Decompose returns the internal decimal state into parts. If the provided buf
// has sufficient capacity, buf may be returned as the coefficient with the
// value set and length set as appropriate. Note that it does not act like
// Append-like functions and does not fill necessarily from the beginning of the
// buffer.
func (d *Decimal) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
switch d.Form {
default:
Expand All @@ -69,7 +71,19 @@ func (d *Decimal) Decompose(buf []byte) (form byte, negative bool, coefficient [
// Finite form.
negative = d.Negative
exponent = d.Exponent
coefficient = d.Coeff.Bytes()

sizeInBytes := (d.Coeff.BitLen() + 8 - 1) / 8 // math.Ceil(d.Coeff.BitLen()/8.0)
if cap(buf)-len(buf) >= sizeInBytes {
// it extends the buffer as the filling of bytes expects an already
// allocated slice.
buf = append(buf, make([]byte, sizeInBytes)...)

// We can fit the coefficient in the given buffer which prevents an
// allocation.
coefficient = d.Coeff.FillBytes(buf)
} else {
coefficient = d.Coeff.Bytes()
}
return
}

Expand Down
107 changes: 107 additions & 0 deletions decomposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
package apd

import (
"bytes"
"encoding/hex"
"fmt"
"math"
"strconv"
"testing"
)

Expand Down Expand Up @@ -109,3 +113,106 @@ func TestDecomposerCompose(t *testing.T) {
})
}
}

func TestDecomposerDecompose_usesTheBufferForCoefficientWithSameSize(t *testing.T) {
tests := []struct {
value string
}{
{"0"},
{"1"},
{strconv.FormatUint(math.MaxUint32, 10)},
{strconv.FormatUint(math.MaxUint64, 10)},
{"18446744073709551616"}, // math.MaxUint64 + 1
{"36893488147419103230"}, // math.MaxUint64 * 2
}

for _, test := range tests {
t.Run(test.value, func(t *testing.T) {
value, _, err := NewFromString(test.value)
if err != nil {
t.Fatal("unexpected error", err)
}

buffer := make([]byte, 0, (value.Coeff.BitLen()+8-1)/8)

_, _, coef, _ := value.Decompose(buffer)
if !bytes.Equal(coef, value.Coeff.Bytes()) {
t.Fatalf("unexpected different coefficients: %s != %s", hex.EncodeToString(coef), hex.EncodeToString(value.Coeff.Bytes()))
}

var res BigInt
res.SetBytes(coef)
if res != value.Coeff {
t.Fatal("unexpected different results")
}
})
}
}

func TestDecomposerDecompose_usesTheBufferForCoefficientWithBiggerSize(t *testing.T) {
tests := []struct {
value string
}{
{"0"},
{"1"},
{strconv.FormatUint(math.MaxUint32, 10)},
{strconv.FormatUint(math.MaxUint64, 10)},
{"18446744073709551616"}, // math.MaxUint64 + 1
{"36893488147419103230"}, // math.MaxUint64 * 2
}

for _, test := range tests {
t.Run(test.value, func(t *testing.T) {
value, _, err := NewFromString(test.value)
if err != nil {
t.Fatal("unexpected error", err)
}

buffer := make([]byte, 0, 64)

_, _, coef, _ := value.Decompose(buffer)
if !bytes.Equal(coef, value.Coeff.Bytes()) {
t.Fatalf("unexpected different coefficients: %s != %s", hex.EncodeToString(coef), hex.EncodeToString(value.Coeff.Bytes()))
}

var res BigInt
res.SetBytes(coef)
if res != value.Coeff {
t.Fatal("unexpected different results")
}
})
}
}

func TestDecomposerDecompose_ignoresBufferIfItDoesNotFit(t *testing.T) {
value := New(42, 0)
buffer := make([]byte, 0)

_, _, coef, _ := value.Decompose(buffer)
if !bytes.Equal([]byte{42}, coef) {
t.Fatal("unexpected different buffers", coef)
}

_, _, coef, _ = value.Decompose(nil)
if !bytes.Equal([]byte{42}, coef) {
t.Fatal("unexpected different buffers with <nil>", coef)
}
}

func BenchmarkDecomposerDecompose(b *testing.B) {
b.Run("no allocation", func(b *testing.B) {
buf := make([]byte, 0, 8)
value := New(42, -1)

for i := 0; i < b.N; i++ {
_, _, _, _ = value.Decompose(buf)
}
})
b.Run("one allocation", func(b *testing.B) {
value := New(42, -1)

for i := 0; i < b.N; i++ {
_, _, _, _ = value.Decompose(nil)
}
})
}

0 comments on commit b1a957e

Please sign in to comment.