From 75bb2cb98e710b1a43ff4611ebdbbbfa3d3314c3 Mon Sep 17 00:00:00 2001 From: Daniel Theophanes Date: Sat, 6 Apr 2019 14:18:57 -0700 Subject: [PATCH] decimal: add decomposer interface For golang/go#30870 --- .travis.yml | 5 ++- decomposer.go | 47 ++++++++++++++++++++++++ decomposer_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 decomposer.go create mode 100644 decomposer_test.go diff --git a/.travis.yml b/.travis.yml index d2d585c..28f68a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ language: go go: - - 1.2 - - 1.3 - - 1.4 + - 1.2.2 + - 1.13 - tip install: diff --git a/decomposer.go b/decomposer.go new file mode 100644 index 0000000..69507c7 --- /dev/null +++ b/decomposer.go @@ -0,0 +1,47 @@ +package decimal + +import ( + "fmt" + "math/big" +) + +// 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) { + negative = d.value.Sign() < 0 + exponent = d.exp + coefficient = d.value.Bytes() + return +} + +const ( + decomposeFinite = 0 + decomposeInfinite = 1 + decomposeNaN = 2 +) + +// 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 decomposeFinite: + // Set rest of finite form below. + case decomposeInfinite: + return fmt.Errorf("Infinite form not supported") + case decomposeNaN: + return fmt.Errorf("NaN form not supported") + } + // Finite form. + if d.value == nil { + d.value = &big.Int{} + } + d.value.SetBytes(coefficient) + if negative && d.value.Sign() >= 0 { + d.value.Neg(d.value) + } + d.exp = exponent + return nil +} diff --git a/decomposer_test.go b/decomposer_test.go new file mode 100644 index 0000000..78a09cd --- /dev/null +++ b/decomposer_test.go @@ -0,0 +1,90 @@ +package decimal + +import ( + "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: "Large-1", S: "9937443000"}, + {N: "Large-2", S: "-9937443000"}, + {N: "AllDecimal-1", S: "0.04828239821"}, + {N: "AllDecimal-2", S: "-0.04828239821"}, + {N: "LargeAndDecimal-1", S: "4378273024.234239278"}, + {N: "LargeAndDecimal-2", S: "-4378273024.234239278"}, + {N: "Zero", S: "0"}, + {N: "One-1", S: "1"}, + {N: "One-2", S: "-1"}, + {N: "LargerThenFloat-1", S: "1234567890123456842"}, + {N: "LargerThenFloat-2", S: "-1234567890123456842"}, + } + for _, item := range list { + d, err := NewFromString(item.S) + if err != nil { + t.Fatal(err) + } + set := &Decimal{} + err = set.Compose(d.Decompose(nil)) + if err == nil && item.E { + t.Fatal("expected error, got ") + } + 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) + } + if set.String() != item.S { + t.Fatalf("string value incorrect, got %q want %q", set.String(), 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{0x01, 0xE2, 0x40}, Exp: -3}, + {N: "Neg-1", S: "-123.456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -3}, + {N: "PosExp-1", S: "123456000", Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, + {N: "PosExp-2", S: "-123456000", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: 3}, + {N: "AllDec-1", S: "0.123456", Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6}, + {N: "AllDec-2", S: "-0.123456", Neg: true, Coef: []byte{0x01, 0xE2, 0x40}, Exp: -6}, + {N: "NaN-1", S: "NaN", Form: 2, Err: true}, + {N: "NaN-2", S: "-NaN", Form: 2, Neg: true, Err: true}, + {N: "Infinity-1", S: "Infinity", Form: 1, Err: true}, + {N: "Infinity-2", S: "-Infinity", Form: 1, Neg: true, Err: true}, + } + + for _, item := range list { + d := &Decimal{} + 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 ") + } + return + } + if s := d.String(); s != item.S { + t.Fatalf("unexpected value, got %q want %q", s, item.S) + } + } +}