decimal/decimal_test.go

584 lines
12 KiB
Go
Raw Normal View History

2015-02-10 21:33:58 +01:00
package decimal
import (
2015-03-23 23:07:27 +01:00
"encoding/json"
2015-03-23 23:14:19 +01:00
"encoding/xml"
2015-02-10 21:33:58 +01:00
"math"
"testing"
)
var testTable = map[float64]string{
3.141592653589793: "3.141592653589793",
3: "3",
1234567890123456: "1234567890123456",
1234567890123456000: "1234567890123456000",
1234.567890123456: "1234.567890123456",
.1234567890123456: "0.1234567890123456",
0: "0",
.1111111111111110: "0.111111111111111",
.1111111111111111: "0.1111111111111111",
.1111111111111119: "0.1111111111111119",
.000000000000000001: "0.000000000000000001",
.000000000000000002: "0.000000000000000002",
.000000000000000003: "0.000000000000000003",
.000000000000000005: "0.000000000000000005",
.000000000000000008: "0.000000000000000008",
.1000000000000001: "0.1000000000000001",
.1000000000000002: "0.1000000000000002",
.1000000000000003: "0.1000000000000003",
.1000000000000005: "0.1000000000000005",
.1000000000000008: "0.1000000000000008",
}
2015-03-23 23:02:29 +01:00
func init() {
2015-02-10 21:33:58 +01:00
// add negatives
for f, s := range testTable {
if f > 0 {
testTable[-f] = "-" + s
}
}
2015-03-23 23:02:29 +01:00
}
2015-02-10 21:33:58 +01:00
2015-03-23 23:02:29 +01:00
func TestNewFromFloat(t *testing.T) {
2015-02-10 21:33:58 +01:00
for f, s := range testTable {
d := NewFromFloat(f)
if d.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, d.String(),
d.value.String(), d.exp)
}
}
shouldPanicOn := []float64{
math.NaN(),
math.Inf(1),
math.Inf(-1),
}
for _, n := range shouldPanicOn {
var d Decimal
if !didPanic(func() { d = NewFromFloat(n) }) {
t.Fatalf("Expected panic when creating a Decimal from %v, got %v instead", n, d.String())
}
}
}
func TestNewFromString(t *testing.T) {
for _, s := range testTable {
d, err := NewFromString(s)
if err != nil {
t.Errorf("error while parsing %s", s)
} else if d.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, d.String(),
d.value.String(), d.exp)
}
}
}
func TestNewFromStringErrs(t *testing.T) {
tests := []string{
"",
"qwert",
"-",
".",
"-.",
".-",
"234-.56",
"234-56",
"2-",
"..",
"2..",
"..2",
".5.2",
"8..2",
"8.1.",
}
for _, s := range tests {
_, err := NewFromString(s)
if err == nil {
t.Errorf("error expected when parsing %s", s)
}
}
}
func TestNewFromFloatWithExponent(t *testing.T) {
type Inp struct {
float float64
exp int32
}
tests := map[Inp]string{
Inp{123.4, -3}: "123.4",
Inp{123.4, -1}: "123.4",
Inp{123.412345, 1}: "120",
Inp{123.412345, 0}: "123",
Inp{123.412345, -5}: "123.41235",
Inp{123.412345, -6}: "123.412345",
Inp{123.412345, -7}: "123.412345",
}
// add negatives
for p, s := range tests {
if p.float > 0 {
tests[Inp{-p.float, p.exp}] = "-" + s
}
}
for input, s := range tests {
d := NewFromFloatWithExponent(input.float, input.exp)
if d.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, d.String(),
d.value.String(), d.exp)
}
}
shouldPanicOn := []float64{
math.NaN(),
math.Inf(1),
math.Inf(-1),
}
for _, n := range shouldPanicOn {
var d Decimal
if !didPanic(func() { d = NewFromFloatWithExponent(n, 0) }) {
t.Fatalf("Expected panic when creating a Decimal from %v, got %v instead", n, d.String())
}
}
}
2015-03-23 23:07:27 +01:00
func TestJSON(t *testing.T) {
for _, s := range testTable {
var doc struct {
Amount Decimal `json:"amount"`
}
docStr := `{"amount":"` + s + `"}`
err := json.Unmarshal([]byte(docStr), &doc)
if err != nil {
t.Errorf("error unmarshaling %s: %v", docStr, err)
} else if doc.Amount.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, doc.Amount.String(),
doc.Amount.value.String(), doc.Amount.exp)
}
out, err := json.Marshal(&doc)
if err != nil {
t.Errorf("error marshaling %+v: %v", doc, err)
} else if string(out) != docStr {
t.Errorf("expected %s, got %s", docStr, string(out))
}
}
}
2015-03-24 01:39:37 +01:00
func TestBadJSON(t *testing.T) {
for _, testCase := range []string{
"]o_o[", "{", `{"amount":""`,
`{"amount":""}`,
`{"amount":"nope"}`,
`0.333`,
} {
var doc struct {
Amount Decimal `json:"amount"`
}
err := json.Unmarshal([]byte(testCase), &doc)
if err == nil {
t.Errorf("expected error, got %+v", doc)
}
}
}
2015-03-23 23:14:19 +01:00
func TestXML(t *testing.T) {
for _, s := range testTable {
var doc struct {
XMLName xml.Name `xml:"account"`
Amount Decimal `xml:"amount"`
}
docStr := `<account><amount>` + s + `</amount></account>`
err := xml.Unmarshal([]byte(docStr), &doc)
if err != nil {
t.Errorf("error unmarshaling %s: %v", docStr, err)
} else if doc.Amount.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, doc.Amount.String(),
doc.Amount.value.String(), doc.Amount.exp)
}
out, err := xml.Marshal(&doc)
if err != nil {
t.Errorf("error marshaling %+v: %v", doc, err)
} else if string(out) != docStr {
t.Errorf("expected %s, got %s", docStr, string(out))
}
}
}
2015-03-24 01:39:37 +01:00
func TestBadXML(t *testing.T) {
for _, testCase := range []string{
"o_o", "<abc", "<account><amount>7",
`<html><body></body></html>`,
`<account><amount></amount></account>`,
`<account><amount>nope</amount></account>`,
`0.333`,
} {
var doc struct {
XMLName xml.Name `xml:"account"`
Amount Decimal `xml:"amount"`
}
err := xml.Unmarshal([]byte(testCase), &doc)
if err == nil {
t.Errorf("expected error, got %+v", doc)
}
}
}
2015-02-10 21:33:58 +01:00
func TestDecimal_rescale(t *testing.T) {
type Inp struct {
int int64
exp int32
rescale int32
}
tests := map[Inp]string{
Inp{1234, -3, -5}: "1.234",
Inp{1234, -3, 0}: "1",
Inp{1234, 3, 0}: "1234000",
Inp{1234, -4, -4}: "0.1234",
}
// add negatives
for p, s := range tests {
if p.int > 0 {
tests[Inp{-p.int, p.exp, p.rescale}] = "-" + s
}
}
for input, s := range tests {
d := New(input.int, input.exp).rescale(input.rescale)
if d.String() != s {
t.Errorf("expected %s, got %s (%s, %d)",
s, d.String(),
d.value.String(), d.exp)
}
// test StringScaled
s2 := New(input.int, input.exp).StringScaled(input.rescale)
if s2 != s {
t.Errorf("expected %s, got %s", s, s2)
}
}
}
func TestDecimal_Uninitialized(t *testing.T) {
a := Decimal{}
b := Decimal{}
decs := []Decimal{
a,
a.rescale(10),
a.Abs(),
a.Add(b),
a.Sub(b),
a.Mul(b),
a.Div(New(1, -1)),
}
for _, d := range decs {
if d.String() != "0" {
t.Errorf("expected 0, got %s", d.String())
}
}
if a.Cmp(b) != 0 {
t.Errorf("a != b")
}
if a.Exponent() != 0 {
t.Errorf("a.Exponent() != 0")
}
}
func TestDecimal_Add(t *testing.T) {
type Inp struct {
a string
b string
}
inputs := map[Inp]string{
Inp{"2", "3"}: "5",
Inp{"2454495034", "3451204593"}: "5905699627",
Inp{"24544.95034", ".3451204593"}: "24545.2954604593",
Inp{".1", ".1"}: "0.2",
Inp{".1", "-.1"}: "0",
Inp{"0", "1.001"}: "1.001",
}
for inp, res := range inputs {
a, err := NewFromString(inp.a)
if err != nil {
t.FailNow()
}
b, err := NewFromString(inp.b)
if err != nil {
t.FailNow()
}
c := a.Add(b)
if c.String() != res {
t.Errorf("expected %s, got %s", res, c.String())
}
}
}
func TestDecimal_Sub(t *testing.T) {
type Inp struct {
a string
b string
}
inputs := map[Inp]string{
Inp{"2", "3"}: "-1",
Inp{"12", "3"}: "9",
Inp{"-2", "9"}: "-11",
Inp{"2454495034", "3451204593"}: "-996709559",
Inp{"24544.95034", ".3451204593"}: "24544.6052195407",
Inp{".1", "-.1"}: "0.2",
Inp{".1", ".1"}: "0",
Inp{"0", "1.001"}: "-1.001",
Inp{"1.001", "0"}: "1.001",
Inp{"2.3", ".3"}: "2",
}
for inp, res := range inputs {
a, err := NewFromString(inp.a)
if err != nil {
t.FailNow()
}
b, err := NewFromString(inp.b)
if err != nil {
t.FailNow()
}
c := a.Sub(b)
if c.String() != res {
t.Errorf("expected %s, got %s", res, c.String())
}
}
}
func TestDecimal_Mul(t *testing.T) {
type Inp struct {
a string
b string
}
inputs := map[Inp]string{
Inp{"2", "3"}: "6",
Inp{"2454495034", "3451204593"}: "8470964534836491162",
Inp{"24544.95034", ".3451204593"}: "8470.964534836491162",
Inp{".1", ".1"}: "0.01",
Inp{"0", "1.001"}: "0",
}
for inp, res := range inputs {
a, err := NewFromString(inp.a)
if err != nil {
t.FailNow()
}
b, err := NewFromString(inp.b)
if err != nil {
t.FailNow()
}
c := a.Mul(b)
if c.String() != res {
t.Errorf("expected %s, got %s", res, c.String())
}
}
// positive scale
c := New(1234, 5).Mul(New(45, -1))
if c.String() != "555300000" {
t.Errorf("Expected %s, got %s", "555300000", c.String())
}
}
func TestDecimal_Div(t *testing.T) {
type Inp struct {
a string
b string
}
inputs := map[Inp]string{
Inp{"6", "3"}: "2",
Inp{"10", "2"}: "5",
Inp{"2.2", "1.1"}: "2",
Inp{"-2.2", "-1.1"}: "2",
Inp{"12.88", "5.6"}: "2.3",
Inp{"1023427554493", "43432632"}: "23563.5628642767953828", // rounded
Inp{"1", "434324545566634"}: "0.0000000000000023",
Inp{"1", "3"}: "0.3333333333333333",
Inp{"2", "3"}: "0.6666666666666667", // rounded
Inp{"10000", "3"}: "3333.3333333333333333",
Inp{"10234274355545544493", "-3"}: "-3411424785181848164.3333333333333333",
Inp{"-4612301402398.4753343454", "23.5"}: "-196268144782.9138440146978723",
}
for inp, expected := range inputs {
num, err := NewFromString(inp.a)
if err != nil {
t.FailNow()
}
denom, err := NewFromString(inp.b)
if err != nil {
t.FailNow()
}
got := num.Div(denom)
if got.String() != expected {
t.Errorf("expected %s when dividing %v by %v, got %v",
expected, num, denom, got)
}
}
type Inp2 struct {
n int64
exp int32
n2 int64
exp2 int32
}
// test code path where exp > 0
inputs2 := map[Inp2]string{
Inp2{124, 10, 3, 1}: "41333333333.3333333333333333",
Inp2{124, 10, 3, 0}: "413333333333.3333333333333333",
Inp2{124, 10, 6, 1}: "20666666666.6666666666666667",
Inp2{124, 10, 6, 0}: "206666666666.6666666666666667",
Inp2{10, 10, 10, 1}: "1000000000",
}
for inp, expectedAbs := range inputs2 {
for i := -1; i <= 1; i += 2 {
for j := -1; j <= 1; j += 2 {
n := inp.n * int64(i)
n2 := inp.n2 * int64(j)
num := New(n, inp.exp)
denom := New(n2, inp.exp2)
expected := expectedAbs
if i != j {
expected = "-" + expectedAbs
}
got := num.Div(denom)
if got.String() != expected {
t.Errorf("expected %s when dividing %v by %v, got %v",
expected, num, denom, got)
}
}
}
}
}
func TestDecimal_Overflow(t *testing.T) {
if !didPanic(func() { New(1, math.MinInt32).Mul(New(1, math.MinInt32)) }) {
t.Fatalf("should have gotten an overflow panic")
}
if !didPanic(func() { New(1, math.MaxInt32).Mul(New(1, math.MaxInt32)) }) {
t.Fatalf("should have gotten an overflow panic")
}
}
2015-03-24 01:44:55 +01:00
func TestIntPart(t *testing.T) {
for _, testCase := range []struct {
Dec string
IntPart int64
}{{"0.01", 0}, {"12.1", 12}, {"9999.999", 9999}, {"-32768.01234", -32768}} {
d, err := NewFromString(testCase.Dec)
if err != nil {
t.Fatal(err)
}
if d.IntPart() != testCase.IntPart {
t.Errorf("expect %d, got %d", testCase.IntPart, d.IntPart())
}
}
}
2015-02-10 21:33:58 +01:00
// old tests after this line
func TestDecimal_Scale(t *testing.T) {
a := New(1234, -3)
if a.Exponent() != -3 {
t.Errorf("error")
}
}
func TestDecimal_Abs1(t *testing.T) {
a := New(-1234, -4)
b := New(1234, -4)
c := a.Abs()
if c.Cmp(b) != 0 {
t.Errorf("error")
}
}
func TestDecimal_Abs2(t *testing.T) {
a := New(-1234, -4)
b := New(1234, -4)
c := b.Abs()
if c.Cmp(a) == 0 {
t.Errorf("error")
}
}
2015-03-23 23:14:19 +01:00
func TestDecimal_Equal(t *testing.T) {
a := New(1234, 3)
b := New(1234, 3)
if !a.Equals(b) {
t.Errorf("Error")
}
}
func TestDecimal_ScalesNotEqual(t *testing.T) {
a := New(1234, 2)
b := New(1234, 3)
if a.Equals(b) {
t.Errorf("Error")
}
}
2015-02-10 21:33:58 +01:00
func TestDecimal_Cmp1(t *testing.T) {
a := New(123, 3)
b := New(-1234, 2)
if a.Cmp(b) != 1 {
t.Errorf("Error")
}
}
func TestDecimal_Cmp2(t *testing.T) {
a := New(123, 3)
b := New(1234, 2)
if a.Cmp(b) != -1 {
t.Errorf("Error")
}
}
func didPanic(f func()) bool {
ret := false
func() {
defer func() {
if message := recover(); message != nil {
ret = true
}
}()
// call the target function
f()
}()
return ret
}