Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apd: add decomposer interface methods #88

Merged
merged 1 commit into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions decomposer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package apd

import (
"fmt"
)

// decomposer composes or decomposes a decimal value to and from individual parts.
// There are four separate parts: a boolean negative flag, a form byte with three possible states
// (finite=0, infinite=1, NaN=2), a base-2 little-endian integer
// coefficient (also known as a significand) as a []byte, and an int32 exponent.
// These are composed into a final value as "decimal = (neg) (form=finite) coefficient * 10 ^ exponent".
// A zero length coefficient is a zero value.
// If the form is not finite the coefficient and scale should be ignored.
// The negative parameter may be set to true for any form, although implementations are not required
// to respect the negative parameter in the non-finite form.
//
// Implementations may choose to signal a negative zero or negative NaN, but implementations
// that do not support these may also ignore the negative zero or negative NaN without error.
// If an implementation does not support Infinity it may be converted into a NaN without error.
// If a value is set that is larger then what is supported by an implementation is attempted to
// be set, an error must be returned.
// 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(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32)

// Compose sets the internal decimal value from parts. If the value cannot be
// represented then an error should be returned.
// The coefficent should not be modified. Successive calls to compose with
// the same arguments should result in the same decimal value.
Compose(form byte, negative bool, coefficient []byte, exponent int32) error
}

// 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.
func (d *Decimal) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
switch d.Form {
default:
panic(fmt.Errorf("unknown Form: %v", d.Form))
case Finite:
// Nothing, continue on.
case Infinite:
negative = d.Negative
form = 1
return
case NaNSignaling, NaN:
negative = d.Negative
form = 2
return
}
// Finite form.
negative = d.Negative
exponent = d.Exponent
// Return big-endian, but need little-endian.
// Returned []byte is a new and can be safely modified in place.
coefficient = d.Coeff.Bytes()

i := 0
j := len(coefficient) - 1
for i < j {
coefficient[i], coefficient[j] = coefficient[j], coefficient[i]
i++
j--
}
return
}

// Compose sets the internal decimal value from parts. If the value cannot be
// represented then an error should be returned.
func (d *Decimal) Compose(form byte, negative bool, coefficient []byte, exponent int32) error {
switch form {
default:
return fmt.Errorf("unknown form: %v", form)
case 0:
d.Form = Finite
// Set rest of finite form below.
case 1:
d.Form = Infinite
d.Negative = negative
return nil
case 2:
d.Form = NaN
d.Negative = negative
return nil
}
// Finite form.
d.Negative = negative

// coefficient is a little-endian, reverse copy for big-endian when copying
// to c2 to avoid modifying coefficient.
c2 := make([]byte, len(coefficient))
for i := len(c2); i > 0; i-- {
c2[i-1] = coefficient[len(c2)-i]
}
d.Coeff.SetBytes(c2)
d.Exponent = exponent
return nil
}
111 changes: 111 additions & 0 deletions decomposer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

package apd

import (
"fmt"
"testing"
)

func TestDecomposerRoundTrip(t *testing.T) {
list := []struct {
N string // Name.
S string // String value.
E bool // Expect an error.
}{
{N: "Zero", S: "0"},
{N: "Normal-1", S: "123.456"},
{N: "Normal-2", S: "-123.456"},
{N: "NaN-1", S: "NaN"},
{N: "NaN-2", S: "-NaN"},
{N: "Infinity-1", S: "Infinity"},
{N: "Infinity-2", S: "-Infinity"},
}
for _, item := range list {
t.Run(item.N, func(t *testing.T) {
d, _, err := NewFromString(item.S)
if err != nil {
t.Fatal(err)
}
set, set2 := &Decimal{}, &Decimal{}
f, n, c, e := d.Decompose(nil)
err = set.Compose(f, n, c, e)
if err == nil && item.E {
t.Fatal("expected error, got <nil>")
}
err = set2.Compose(f, n, c, e)
if err == nil && item.E {
t.Fatal("expected error, got <nil>")
}
if set.Cmp(set2) != 0 {
t.Fatalf("composing the same value twice resulted in different values. set=%v set2=%v", set, set2)
}
if err != nil && !item.E {
t.Fatalf("unexpected error: %v", err)
}
if set.Cmp(d) != 0 {
t.Fatalf("values incorrect, got %v want %v (%s)", set, d, item.S)
}
})
}
}

func TestDecomposerCompose(t *testing.T) {
list := []struct {
N string // Name.
S string // String value.

Form byte // Form
Neg bool
Coef []byte // Coefficent
Exp int32

Err bool // Expect an error.
}{
{N: "Zero", S: "0", Coef: nil, Exp: 0},
{N: "Normal-1", S: "123.456", Coef: []byte{0x40, 0xE2, 0x01}, Exp: -3},
{N: "Neg-1", S: "-123.456", Neg: true, Coef: []byte{0x40, 0xE2, 0x01}, Exp: -3},
{N: "PosExp-1", S: "123456000", Coef: []byte{0x40, 0xE2, 0x01}, Exp: 3},
{N: "PosExp-2", S: "-123456000", Neg: true, Coef: []byte{0x40, 0xE2, 0x01}, Exp: 3},
{N: "AllDec-1", S: "0.123456", Coef: []byte{0x40, 0xE2, 0x01}, Exp: -6},
{N: "AllDec-2", S: "-0.123456", Neg: true, Coef: []byte{0x40, 0xE2, 0x01}, Exp: -6},
{N: "NaN-1", S: "NaN", Form: 2},
{N: "NaN-2", S: "-NaN", Form: 2, Neg: true},
{N: "Infinity-1", S: "Infinity", Form: 1},
{N: "Infinity-2", S: "-Infinity", Form: 1, Neg: true},
}

for _, item := range list {
t.Run(item.N, func(t *testing.T) {
d, _, err := NewFromString(item.S)
if err != nil {
t.Fatal(err)
}
err = d.Compose(item.Form, item.Neg, item.Coef, item.Exp)
if err != nil && !item.Err {
t.Fatalf("unexpected error, got %v", err)
}
if item.Err {
if err == nil {
t.Fatal("expected error, got <nil>")
}
return
}
if s := fmt.Sprintf("%f", d); s != item.S {
t.Fatalf("unexpected value, got %q want %q", s, item.S)
}
})
}
}