-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
For golang/go#30870
- Loading branch information
Showing
2 changed files
with
202 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,113 @@ | ||
package decimal | ||
|
||
import ( | ||
"encoding/binary" | ||
"fmt" | ||
"math" | ||
"math/big" | ||
) | ||
|
||
// 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. | ||
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 (z *Big) Decompose(buf []byte) (form byte, negative bool, coefficient []byte, exponent int32) { | ||
negative = z.Sign() < 0 | ||
switch { | ||
case z.IsInf(0): | ||
form = 1 | ||
return | ||
case z.IsNaN(0): | ||
form = 2 | ||
return | ||
} | ||
if !z.IsFinite() { | ||
panic("expected number to be finite") | ||
} | ||
if z.exp > math.MaxInt32 { | ||
panic("exponent exceeds max size") | ||
} | ||
exponent = int32(z.exp) | ||
|
||
if z.isCompact() { | ||
if cap(buf) >= 16 { | ||
coefficient = buf[:16] | ||
} else { | ||
coefficient = make([]byte, 16) | ||
} | ||
binary.LittleEndian.PutUint64(coefficient, z.compact) | ||
} else { | ||
coefficient = z.unscaled.Bytes() // This returns a big-endian slice. | ||
// Swap bytes for little endian. | ||
|
||
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 (z *Big) Compose(form byte, negative bool, coefficient []byte, exponent int32) error { | ||
switch form { | ||
default: | ||
return fmt.Errorf("unknown form: %v", form) | ||
case 0: | ||
// Finite form below. | ||
case 1: | ||
z.SetInf(negative) | ||
return nil | ||
case 2: | ||
z.SetNaN(false) | ||
return nil | ||
} | ||
// Finite form. | ||
|
||
// bigint expect bytes in big-endian form. | ||
i := 0 | ||
j := len(coefficient) - 1 | ||
for i < j { | ||
coefficient[i], coefficient[j] = coefficient[j], coefficient[i] | ||
i++ | ||
j-- | ||
} | ||
c := &big.Int{} | ||
c.SetBytes(coefficient) | ||
z.SetBigMantScale(c, -int(exponent)) | ||
if negative { | ||
z.Neg(z) | ||
} | ||
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,89 @@ | ||
package decimal | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestDecomposerRoundTrip(t *testing.T) { | ||
list := []struct { | ||
N string // Name. | ||
S string // String value. | ||
E bool // Expect an error. | ||
}{ | ||
{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 := &Big{} | ||
d, ok := d.SetString(item.S) | ||
if !ok { | ||
t.Fatal("unable to set value") | ||
} | ||
set := &Big{} | ||
err := set.Compose(d.Decompose(nil)) | ||
if err == nil && item.E { | ||
t.Fatal("expected error, got <nil>") | ||
} | ||
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: "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 := &Big{} | ||
d, ok := d.SetString(item.S) | ||
if !ok { | ||
t.Fatal("unable to set value") | ||
} | ||
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) | ||
} | ||
}) | ||
} | ||
} |