Skip to content

Commit

Permalink
Merge pull request #132 from Gilthoniel/feature/decompose-buffer
Browse files Browse the repository at this point in the history
Allow decompose to make use of the buffer
  • Loading branch information
nvanbenschoten authored Jun 10, 2024
2 parents e8d9190 + e639d5f commit 1ebf545
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 9 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ jobs:
- armv7
- aarch64
go:
- '1.13'
- '1.14'
- '1.15'
- '1.16'
- '1.17'
Expand Down
30 changes: 23 additions & 7 deletions decomposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ import "fmt"
// Implementations must return an error if a NaN or Infinity is attempted to be set while neither
// are supported.
type decomposer interface {
// 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.
Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32)

// Compose sets the internal decimal value from parts. If the value cannot be
Expand All @@ -48,9 +50,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 +73,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) >= sizeInBytes {
// It extends the buffer as the filling of bytes expects an already
// allocated slice.
buf = buf[: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
137 changes: 137 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,136 @@ 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 TestDecomposerDecompose_usesBufferWithNonZeroLength(t *testing.T) {
value := New(42, 0)
buffer := make([]byte, 4)

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

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

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

func TestDecomposerDecompose_extendsBufferWithNonZeroLength(t *testing.T) {
value := New(math.MaxInt64, 0)
buffer := make([]byte, 2)

_, _, coef, _ := value.Decompose(buffer)
if !bytes.Equal([]byte{127, 255, 255, 255, 255, 255, 255, 255}, coef) {
t.Fatal("unexpected different buffers", 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 1ebf545

Please sign in to comment.