Skip to content

Commit 3627411

Browse files
committed
decimal: add decomposer interface
For golang/go#30870
1 parent b4c50b8 commit 3627411

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

decomposer.go

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package decimal
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"math"
7+
"math/big"
8+
)
9+
10+
// decomposer composes or decomposes a decimal value to and from individual parts.
11+
// There are four separate parts: a boolean negative flag, a form byte with three possible states
12+
// (finite=0, infinite=1, NaN=2), a base-2 big-endian integer
13+
// coefficient (also known as a significand) as a []byte, and an int32 exponent.
14+
// These are composed into a final value as "decimal = (neg) (form=finite) coefficient * 10 ^ exponent".
15+
// A zero length coefficient is a zero value.
16+
// If the form is not finite the coefficient and scale should be ignored.
17+
// The negative parameter may be set to true for any form, although implementations are not required
18+
// to respect the negative parameter in the non-finite form.
19+
//
20+
// Implementations may choose to signal a negative zero or negative NaN, but implementations
21+
// that do not support these may also ignore the negative zero or negative NaN without error.
22+
// If an implementation does not support Infinity it may be converted into a NaN without error.
23+
// If a value is set that is larger then what is supported by an implementation is attempted to
24+
// be set, an error must be returned.
25+
// Implementations must return an error if a NaN or Infinity is attempted to be set while neither
26+
// are supported.
27+
type decomposer interface {
28+
// Decompose returns the internal decimal state into parts.
29+
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
30+
// the value set and length set as appropriate.
31+
Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32)
32+
33+
// Compose sets the internal decimal value from parts. If the value cannot be
34+
// represented then an error should be returned.
35+
// The coefficent should not be modified. Successive calls to compose with
36+
// the same arguments should result in the same decimal value.
37+
Compose(form byte, negative bool, coefficient []byte, exponent int32) error
38+
}
39+
40+
// Decompose returns the internal decimal state into parts.
41+
// If the provided buf has sufficient capacity, buf may be returned as the coefficient with
42+
// the value set and length set as appropriate.
43+
func (z *Big) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) {
44+
negative = z.Sign() < 0
45+
switch {
46+
case z.IsInf(0):
47+
form = 1
48+
return
49+
case z.IsNaN(0):
50+
form = 2
51+
return
52+
}
53+
if !z.IsFinite() {
54+
panic("expected number to be finite")
55+
}
56+
if z.exp > math.MaxInt32 {
57+
panic("exponent exceeds max size")
58+
}
59+
exponent = int32(z.exp)
60+
61+
if z.isCompact() {
62+
if cap(buf) >= 8 {
63+
coefficient = buf[:8]
64+
} else {
65+
coefficient = make([]byte, 8)
66+
}
67+
binary.BigEndian.PutUint64(coefficient, z.compact)
68+
} else {
69+
coefficient = z.unscaled.Bytes() // This returns a big-endian slice.
70+
}
71+
return
72+
}
73+
74+
// Compose sets the internal decimal value from parts. If the value cannot be
75+
// represented then an error should be returned.
76+
func (z *Big) Compose(form byte, negative bool, coefficient []byte, exponent int32) error {
77+
switch form {
78+
default:
79+
return fmt.Errorf("unknown form: %v", form)
80+
case 0:
81+
// Finite form below.
82+
case 1:
83+
z.SetInf(negative)
84+
return nil
85+
case 2:
86+
z.SetNaN(false)
87+
return nil
88+
}
89+
bigc := &big.Int{}
90+
bigc.SetBytes(coefficient)
91+
z.SetBigMantScale(bigc, -int(exponent))
92+
if negative {
93+
z.Neg(z)
94+
}
95+
return nil
96+
}

decomposer_test.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package decimal
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestDecomposerRoundTrip(t *testing.T) {
9+
list := []struct {
10+
N string // Name.
11+
S string // String value.
12+
E bool // Expect an error.
13+
}{
14+
{N: "Normal-1", S: "123.456"},
15+
{N: "Normal-2", S: "-123.456"},
16+
{N: "NaN-1", S: "NaN"},
17+
{N: "NaN-2", S: "-NaN"},
18+
{N: "Infinity-1", S: "Infinity"},
19+
{N: "Infinity-2", S: "-Infinity"},
20+
}
21+
for _, item := range list {
22+
t.Run(item.N, func(t *testing.T) {
23+
d := &Big{}
24+
d, ok := d.SetString(item.S)
25+
if !ok {
26+
t.Fatal("unable to set value")
27+
}
28+
set, set2 := &Big{}, &Big{}
29+
f, n, c, e := d.Decompose(nil)
30+
err := set.Compose(f, n, c, e)
31+
if err == nil && item.E {
32+
t.Fatal("expected error, got <nil>")
33+
}
34+
err = set2.Compose(f, n, c, e)
35+
if err == nil && item.E {
36+
t.Fatal("expected error, got <nil>")
37+
}
38+
if set.Cmp(set2) != 0 {
39+
t.Fatalf("composing the same value twice resulted in different values. set=%v set2=%v", set, set2)
40+
}
41+
42+
if err != nil && !item.E {
43+
t.Fatalf("unexpected error: %v", err)
44+
}
45+
if set.Cmp(d) != 0 {
46+
t.Fatalf("values incorrect, got %v want %v (%s)", set, d, item.S)
47+
}
48+
})
49+
}
50+
}
51+
52+
func TestDecomposerCompose(t *testing.T) {
53+
list := []struct {
54+
N string // Name.
55+
S string // String value.
56+
57+
Form byte // Form
58+
Neg bool
59+
Coef []byte // Coefficent
60+
Exp int32
61+
62+
Err bool // Expect an error.
63+
}{
64+
{N: "Zero", S: "0", Coef: nil, Exp: 0},
65+
{N: "Normal-1", S: "123.456", Coef: []byte{0x01, 0xE2, 0x40}, Exp: -3},
66+
{N: "Neg-1", S: "-123.456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -3},
67+
{N: "PosExp-1", S: "123456000", Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3},
68+
{N: "PosExp-2", S: "-123456000", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3},
69+
{N: "AllDec-1", S: "0.123456", Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6},
70+
{N: "AllDec-2", S: "-0.123456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6},
71+
{N: "NaN-1", S: "NaN", Form: 2},
72+
{N: "Infinity-1", S: "Infinity", Form: 1},
73+
{N: "Infinity-2", S: "-Infinity", Form: 1, Neg: true},
74+
}
75+
76+
for _, item := range list {
77+
t.Run(item.N, func(t *testing.T) {
78+
d := &Big{}
79+
d, ok := d.SetString(item.S)
80+
if !ok {
81+
t.Fatal("unable to set value")
82+
}
83+
err := d.Compose(item.Form, item.Neg, item.Coef, item.Exp)
84+
if err != nil && !item.Err {
85+
t.Fatalf("unexpected error, got %v", err)
86+
}
87+
if item.Err {
88+
if err == nil {
89+
t.Fatal("expected error, got <nil>")
90+
}
91+
return
92+
}
93+
if s := fmt.Sprintf("%f", d); s != item.S {
94+
t.Fatalf("unexpected value, got %q want %q", s, item.S)
95+
}
96+
})
97+
}
98+
}

0 commit comments

Comments
 (0)