-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
apd: add decomposer interface methods
This implements the interface as worked on in the issue https://golang.org/issue/30870 .
- Loading branch information
Showing
2 changed files
with
232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// 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, swap for big-endian. | ||
c2 := make([]byte, len(coefficient)) | ||
copy(c2, coefficient) | ||
coefficient = c2 | ||
i := 0 | ||
j := len(coefficient) - 1 | ||
for i < j { | ||
coefficient[i], coefficient[j] = coefficient[j], coefficient[i] | ||
i++ | ||
j-- | ||
} | ||
d.Coeff.SetBytes(coefficient) | ||
d.Exponent = exponent | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |