diff --git a/Makefile b/Makefile index 58b8cc94b..10b8e9871 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ fmt: go fmt ./... vet: - go vet $(go list ./... | grep -v /vendor) + go vet `go list ./... | grep -v /vendor | grep -v fix4 | grep -v fix5 | grep -v fixt` lint: go get github.com/golang/lint/golint @@ -17,7 +17,7 @@ test: go test -v -cover . ./datadictionary ./internal _build_all: - go build -v ./... + go build -v `go list ./... | grep -v /vendor` build_accept: cd _test; go build -o echo_server diff --git a/cmd/generate-fix/generate-fix.go b/cmd/generate-fix/generate-fix.go index c5ecdde6c..5ec5ff47a 100644 --- a/cmd/generate-fix/generate-fix.go +++ b/cmd/generate-fix/generate-fix.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "text/template" "github.com/quickfixgo/quickfix/cmd/generate-fix/internal" "github.com/quickfixgo/quickfix/datadictionary" @@ -53,48 +54,28 @@ type component struct { } func genHeader(pkg string, spec *datadictionary.DataDictionary) { - defer waitGroup.Done() - writer := new(bytes.Buffer) c := component{ Package: pkg, Name: "Header", MessageDef: spec.Header, FIXSpec: spec, } - if err := internal.HeaderTemplate.Execute(writer, c); err != nil { - errors <- err - return - } - - if err := internal.WriteFile(path.Join(pkg, "header.generated.go"), writer.String()); err != nil { - errors <- err - } + gen(internal.HeaderTemplate, path.Join(pkg, "header.generated.go"), c) } func genTrailer(pkg string, spec *datadictionary.DataDictionary) { - defer waitGroup.Done() - writer := new(bytes.Buffer) c := component{ Package: pkg, Name: "Trailer", MessageDef: spec.Trailer, } - if err := internal.TrailerTemplate.Execute(writer, c); err != nil { - errors <- err - return - } - - if err := internal.WriteFile(path.Join(pkg, "trailer.generated.go"), writer.String()); err != nil { - errors <- err - } + gen(internal.TrailerTemplate, path.Join(pkg, "trailer.generated.go"), c) } func genMessage(fixPkg string, spec *datadictionary.DataDictionary, msg *datadictionary.MessageDef) { - defer waitGroup.Done() pkgName := strings.ToLower(msg.Name) transportPkg := getTransportPackageName(spec) - writer := new(bytes.Buffer) c := component{ Package: pkgName, FIXPackage: fixPkg, @@ -104,54 +85,31 @@ func genMessage(fixPkg string, spec *datadictionary.DataDictionary, msg *datadic MessageDef: msg, } - if err := internal.MessageTemplate.Execute(writer, c); err != nil { - errors <- err - return - } - - if err := internal.WriteFile(path.Join(fixPkg, pkgName, msg.Name+".generated.go"), writer.String()); err != nil { - errors <- err - } + gen(internal.MessageTemplate, path.Join(fixPkg, pkgName, msg.Name+".generated.go"), c) } func genTags() { - defer waitGroup.Done() - writer := new(bytes.Buffer) - - if err := internal.TagTemplate.Execute(writer, internal.GlobalFieldTypes); err != nil { - errors <- err - return - } - - if err := internal.WriteFile("tag/tag_numbers.generated.go", writer.String()); err != nil { - errors <- err - } + gen(internal.TagTemplate, "tag/tag_numbers.generated.go", internal.GlobalFieldTypes) } func genFields() { - defer waitGroup.Done() - writer := new(bytes.Buffer) - - if err := internal.FieldTemplate.Execute(writer, internal.GlobalFieldTypes); err != nil { - errors <- err - return - } - - if err := internal.WriteFile("field/fields.generated.go", writer.String()); err != nil { - errors <- err - } + gen(internal.FieldTemplate, "field/fields.generated.go", internal.GlobalFieldTypes) } func genEnums() { + gen(internal.EnumTemplate, "enum/enums.generated.go", internal.GlobalFieldTypes) +} + +func gen(t *template.Template, fileOut string, data interface{}) { defer waitGroup.Done() writer := new(bytes.Buffer) - if err := internal.EnumTemplate.Execute(writer, internal.GlobalFieldTypes); err != nil { + if err := t.Execute(writer, data); err != nil { errors <- err return } - if err := internal.WriteFile("enum/enums.generated.go", writer.String()); err != nil { + if err := internal.WriteFile(fileOut, writer.String()); err != nil { errors <- err } } diff --git a/fix_decimal.go b/fix_decimal.go new file mode 100644 index 000000000..d6f8ab1cd --- /dev/null +++ b/fix_decimal.go @@ -0,0 +1,20 @@ +package quickfix + +import "github.com/shopspring/decimal" + +//FIXDecimal is a FIX Float Value that implements an arbitrary precision fixed-point decimal. Implements FieldValue +type FIXDecimal struct { + decimal.Decimal + + //Scale is the number of digits after the decimal point when Writing the field value as a FIX value + Scale int32 +} + +func (d FIXDecimal) Write() []byte { + return []byte(d.Decimal.StringFixed(d.Scale)) +} + +func (d *FIXDecimal) Read(bytes []byte) (err error) { + d.Decimal, err = decimal.NewFromString(string(bytes)) + return +} diff --git a/fix_decimal_test.go b/fix_decimal_test.go new file mode 100644 index 000000000..1785d6e67 --- /dev/null +++ b/fix_decimal_test.go @@ -0,0 +1,51 @@ +package quickfix + +import ( + "testing" + + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFIXDecimalWrite(t *testing.T) { + var tests = []struct { + decimal FIXDecimal + expected string + }{ + {decimal: FIXDecimal{Decimal: decimal.New(-1243456, -4), Scale: 4}, expected: "-124.3456"}, + {decimal: FIXDecimal{Decimal: decimal.New(-1243456, -4), Scale: 5}, expected: "-124.34560"}, + {decimal: FIXDecimal{Decimal: decimal.New(-1243456, -4), Scale: 0}, expected: "-124"}, + } + + for _, test := range tests { + b := test.decimal.Write() + assert.Equal(t, test.expected, string(b)) + } +} + +func TestFIXDecimalRead(t *testing.T) { + var tests = []struct { + bytes string + expected decimal.Decimal + expectError bool + }{ + {bytes: "15", expected: decimal.New(15, 0)}, + {bytes: "15.000", expected: decimal.New(15, 0)}, + {bytes: "15.001", expected: decimal.New(15001, -3)}, + {bytes: "-15.001", expected: decimal.New(-15001, -3)}, + {bytes: "blah", expectError: true}, + {bytes: "+200.00", expected: decimal.New(200, 0)}, + } + + for _, test := range tests { + var field FIXDecimal + + err := field.Read([]byte(test.bytes)) + require.Equal(t, test.expectError, err != nil) + + if !test.expectError { + assert.True(t, test.expected.Equals(field.Decimal), "Expected %s got %s", test.expected, field.Decimal) + } + } +} diff --git a/fix_float_test.go b/fix_float_test.go index e34fa7d38..b31bdbb60 100644 --- a/fix_float_test.go +++ b/fix_float_test.go @@ -51,6 +51,6 @@ func BenchmarkFloatRead(b *testing.B) { val := []byte("15.1234") for i := 0; i < b.N; i++ { var field FIXFloat - field.Read(val) + _ = field.Read(val) } } diff --git a/vendor/github.com/shopspring/decimal/LICENSE b/vendor/github.com/shopspring/decimal/LICENSE new file mode 100644 index 000000000..ad2148aaf --- /dev/null +++ b/vendor/github.com/shopspring/decimal/LICENSE @@ -0,0 +1,45 @@ +The MIT License (MIT) + +Copyright (c) 2015 Spring, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +- Based on https://github.com/oguzbilgic/fpd, which has the following license: +""" +The MIT License (MIT) + +Copyright (c) 2013 Oguz Bilgic + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +""" diff --git a/vendor/github.com/shopspring/decimal/README.md b/vendor/github.com/shopspring/decimal/README.md new file mode 100644 index 000000000..7aaadaef6 --- /dev/null +++ b/vendor/github.com/shopspring/decimal/README.md @@ -0,0 +1,124 @@ +# decimal [![Build Status](https://travis-ci.org/shopspring/decimal.png?branch=master)](https://travis-ci.org/shopspring/decimal) [![BADGINATOR](https://badginator.herokuapp.com/shopspring/decimal.svg?image_analysis=1)](https://github.com/defunctzombie/badginator) + +Arbitrary-precision fixed-point decimal numbers in go. + +NOTE: can "only" represent numbers with a maximum of 2^31 digits after the decimal point. + +## Features + + * the zero-value is 0, and is safe to use without initialization + * addition, subtraction, multiplication with no loss of precision + * division with specified precision + * database/sql serialization/deserialization + * json and xml serialization/deserialization + +## Install + +Run `go get github.com/shopspring/decimal` + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/shopspring/decimal" +) + +func main() { + price, err := decimal.NewFromString("136.02") + if err != nil { + panic(err) + } + + quantity := decimal.NewFromFloat(3) + + fee, _ := decimal.NewFromString(".035") + taxRate, _ := decimal.NewFromString(".08875") + + subtotal := price.Mul(quantity) + + preTax := subtotal.Mul(fee.Add(decimal.NewFromFloat(1))) + + total := preTax.Mul(taxRate.Add(decimal.NewFromFloat(1))) + + fmt.Println("Subtotal:", subtotal) // Subtotal: 408.06 + fmt.Println("Pre-tax:", preTax) // Pre-tax: 422.3421 + fmt.Println("Taxes:", total.Sub(preTax)) // Taxes: 37.482861375 + fmt.Println("Total:", total) // Total: 459.824961375 + fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) // Tax rate: 0.08875 +} +``` + +## Documentation + +http://godoc.org/github.com/shopspring/decimal + +## Production Usage + +* [Spring](https://shopspring.com/), since August 14, 2014. +* If you are using this in production, please let us know! + +## FAQ + +#### Why don't you just use float64? + +Because float64s (or any binary floating point type, actually) can't represent +numbers such as 0.1 exactly. + +Consider this code: http://play.golang.org/p/TQBd4yJe6B You might expect that +it prints out `10`, but it actually prints `9.999999999999831`. Over time, +these small errors can really add up! + +#### Why don't you just use big.Rat? + +big.Rat is fine for representing rational numbers, but Decimal is better for +representing money. Why? Here's a (contrived) example: + +Let's say you use big.Rat, and you have two numbers, x and y, both +representing 1/3, and you have `z = 1 - x - y = 1/3`. If you print each one +out, the string output has to stop somewhere (let's say it stops at 3 decimal +digits, for simplicity), so you'll get 0.333, 0.333, and 0.333. But where did +the other 0.001 go? + +Here's the above example as code: http://play.golang.org/p/lCZZs0w9KE + +With Decimal, the strings being printed out represent the number exactly. So, +if you have `x = y = 1/3` (with precision 3), they will actually be equal to +0.333, and when you do `z = 1 - x - y`, `z` will be equal to .334. No money is +unaccounted for! + +You still have to be careful. If you want to split a number `N` 3 ways, you +can't just send `N/3` to three different people. You have to pick one to send +`N - (2/3*N)` to. That person will receive the fraction of a penny remainder. + +But, it is much easier to be careful with Decimal than with big.Rat. + +#### Why isn't the API similar to big.Int's? + +big.Int's API is built to reduce the number of memory allocations for maximal +performance. This makes sense for its use-case, but the trade-off is that the +API is awkward and easy to misuse. + +For example, to add two big.Ints, you do: `z := new(big.Int).Add(x, y)`. A +developer unfamiliar with this API might try to do `z := a.Add(a, b)`. This +modifies `a` and sets `z` as an alias for `a`, which they might not expect. It +also modifies any other aliases to `a`. + +Here's an example of the subtle bugs you can introduce with big.Int's API: +https://play.golang.org/p/x2R_78pa8r + +In contrast, it's difficult to make such mistakes with decimal. Decimals +behave like other go numbers types: even though `a = b` will not deep copy +`b` into `a`, it is impossible to modify a Decimal, since all Decimal methods +return new Decimals and do not modify the originals. The downside is that +this causes extra allocations, so Decimal is less performant. My assumption +is that if you're using Decimals, you probably care more about correctness +than performance. + +## License + +The MIT License (MIT) + +This is a heavily modified fork of [fpd.Decimal](https://github.com/oguzbilgic/fpd), which was also released under the MIT License. diff --git a/vendor/github.com/shopspring/decimal/decimal.go b/vendor/github.com/shopspring/decimal/decimal.go new file mode 100644 index 000000000..287f2c595 --- /dev/null +++ b/vendor/github.com/shopspring/decimal/decimal.go @@ -0,0 +1,652 @@ +// Package decimal implements an arbitrary precision fixed-point decimal. +// +// To use as part of a struct: +// +// type Struct struct { +// Number Decimal +// } +// +// The zero-value of a Decimal is 0, as you would expect. +// +// The best way to create a new Decimal is to use decimal.NewFromString, ex: +// +// n, err := decimal.NewFromString("-123.4567") +// n.String() // output: "-123.4567" +// +// NOTE: This can "only" represent numbers with a maximum of 2^31 digits +// after the decimal point. +package decimal + +import ( + "database/sql/driver" + "fmt" + "math" + "math/big" + "strconv" + "strings" +) + +// DivisionPrecision is the number of decimal places in the result when it +// doesn't divide exactly. +// +// Example: +// +// d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3) +// d1.String() // output: "0.6666666666666667" +// d2 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(30000) +// d2.String() // output: "0.0000666666666667" +// d3 := decimal.NewFromFloat(20000).Div(decimal.NewFromFloat(3) +// d3.String() // output: "6666.6666666666666667" +// decimal.DivisionPrecision = 3 +// d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3) +// d4.String() // output: "0.667" +// +var DivisionPrecision = 16 + +// Zero constant, to make computations faster. +var Zero = New(0, 1) + +var zeroInt = big.NewInt(0) +var oneInt = big.NewInt(1) +var fiveInt = big.NewInt(5) +var tenInt = big.NewInt(10) + +// Decimal represents a fixed-point decimal. It is immutable. +// number = value * 10 ^ exp +type Decimal struct { + value *big.Int + + // NOTE(vadim): this must be an int32, because we cast it to float64 during + // calculations. If exp is 64 bit, we might lose precision. + // If we cared about being able to represent every possible decimal, we + // could make exp a *big.Int but it would hurt performance and numbers + // like that are unrealistic. + exp int32 +} + +// New returns a new fixed-point decimal, value * 10 ^ exp. +func New(value int64, exp int32) Decimal { + return Decimal{ + value: big.NewInt(value), + exp: exp, + } +} + +// NewFromString returns a new Decimal from a string representation. +// +// Example: +// +// d, err := NewFromString("-123.45") +// d2, err := NewFromString(".0001") +// +func NewFromString(value string) (Decimal, error) { + originalInput := value + var intString string + var exp int64 + + // Check if number is using scientific notation + eIndex := strings.IndexAny(value, "Ee") + if eIndex != -1 { + expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32) + if err != nil { + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", value) + } + return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent is not numeric", value) + } + value = value[:eIndex] + exp = expInt + } + + parts := strings.Split(value, ".") + if len(parts) == 1 { + // There is no decimal point, we can just parse the original string as + // an int + intString = value + } else if len(parts) == 2 { + intString = parts[0] + parts[1] + expInt := -len(parts[1]) + exp += int64(expInt) + } else { + return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value) + } + + dValue := new(big.Int) + _, ok := dValue.SetString(intString, 10) + if !ok { + return Decimal{}, fmt.Errorf("can't convert %s to decimal", value) + } + + if exp < math.MinInt32 || exp > math.MaxInt32 { + // NOTE(vadim): I doubt a string could realistically be this long + return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", originalInput) + } + + return Decimal{ + value: dValue, + exp: int32(exp), + }, nil +} + +// NewFromFloat converts a float64 to Decimal. +// +// Example: +// +// NewFromFloat(123.45678901234567).String() // output: "123.4567890123456" +// NewFromFloat(.00000000000000001).String() // output: "0.00000000000000001" +// +// NOTE: this will panic on NaN, +/-inf +func NewFromFloat(value float64) Decimal { + floor := math.Floor(value) + + // fast path, where float is an int + if floor == value && value <= math.MaxInt64 && value >= math.MinInt64 { + return New(int64(value), 0) + } + + // slow path: float is a decimal + // HACK(vadim): do this the slow hacky way for now because the logic to + // convert a base-2 float to base-10 properly is not trivial + str := strconv.FormatFloat(value, 'f', -1, 64) + dec, err := NewFromString(str) + if err != nil { + panic(err) + } + return dec +} + +// NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary +// number of fractional digits. +// +// Example: +// +// NewFromFloatWithExponent(123.456, -2).String() // output: "123.46" +// +func NewFromFloatWithExponent(value float64, exp int32) Decimal { + mul := math.Pow(10, -float64(exp)) + floatValue := value * mul + if math.IsNaN(floatValue) || math.IsInf(floatValue, 0) { + panic(fmt.Sprintf("Cannot create a Decimal from %v", floatValue)) + } + dValue := big.NewInt(round(floatValue)) + + return Decimal{ + value: dValue, + exp: exp, + } +} + +// rescale returns a rescaled version of the decimal. Returned +// decimal may be less precise if the given exponent is bigger +// than the initial exponent of the Decimal. +// NOTE: this will truncate, NOT round +// +// Example: +// +// d := New(12345, -4) +// d2 := d.rescale(-1) +// d3 := d2.rescale(-4) +// println(d1) +// println(d2) +// println(d3) +// +// Output: +// +// 1.2345 +// 1.2 +// 1.2000 +// +func (d Decimal) rescale(exp int32) Decimal { + d.ensureInitialized() + // NOTE(vadim): must convert exps to float64 before - to prevent overflow + diff := math.Abs(float64(exp) - float64(d.exp)) + value := new(big.Int).Set(d.value) + + expScale := new(big.Int).Exp(tenInt, big.NewInt(int64(diff)), nil) + if exp > d.exp { + value = value.Quo(value, expScale) + } else if exp < d.exp { + value = value.Mul(value, expScale) + } + + return Decimal{ + value: value, + exp: exp, + } +} + +// Abs returns the absolute value of the decimal. +func (d Decimal) Abs() Decimal { + d.ensureInitialized() + d2Value := new(big.Int).Abs(d.value) + return Decimal{ + value: d2Value, + exp: d.exp, + } +} + +// Add returns d + d2. +func (d Decimal) Add(d2 Decimal) Decimal { + baseScale := min(d.exp, d2.exp) + rd := d.rescale(baseScale) + rd2 := d2.rescale(baseScale) + + d3Value := new(big.Int).Add(rd.value, rd2.value) + return Decimal{ + value: d3Value, + exp: baseScale, + } +} + +// Sub returns d - d2. +func (d Decimal) Sub(d2 Decimal) Decimal { + baseScale := min(d.exp, d2.exp) + rd := d.rescale(baseScale) + rd2 := d2.rescale(baseScale) + + d3Value := new(big.Int).Sub(rd.value, rd2.value) + return Decimal{ + value: d3Value, + exp: baseScale, + } +} + +// Mul returns d * d2. +func (d Decimal) Mul(d2 Decimal) Decimal { + d.ensureInitialized() + d2.ensureInitialized() + + expInt64 := int64(d.exp) + int64(d2.exp) + if expInt64 > math.MaxInt32 || expInt64 < math.MinInt32 { + // NOTE(vadim): better to panic than give incorrect results, as + // Decimals are usually used for money + panic(fmt.Sprintf("exponent %v overflows an int32!", expInt64)) + } + + d3Value := new(big.Int).Mul(d.value, d2.value) + return Decimal{ + value: d3Value, + exp: int32(expInt64), + } +} + +// Div returns d / d2. If it doesn't divide exactly, the result will have +// DivisionPrecision digits after the decimal point. +func (d Decimal) Div(d2 Decimal) Decimal { + // NOTE(vadim): division is hard, use Rat to do it + ratNum := d.Rat() + ratDenom := d2.Rat() + + quoRat := big.NewRat(0, 1).Quo(ratNum, ratDenom) + + // HACK(vadim): converting from Rat to Decimal inefficiently for now + ret, err := NewFromString(quoRat.FloatString(DivisionPrecision)) + if err != nil { + panic(err) // this should never happen + } + return ret +} + +// Mod returns d % d2. +func (d Decimal) Mod(d2 Decimal) Decimal { + quo := d.Div(d2).Truncate(0) + return d.Sub(d2.Mul(quo)) +} + +// Cmp compares the numbers represented by d and d2 and returns: +// +// -1 if d < d2 +// 0 if d == d2 +// +1 if d > d2 +// +func (d Decimal) Cmp(d2 Decimal) int { + d.ensureInitialized() + d2.ensureInitialized() + + if d.exp == d2.exp { + return d.value.Cmp(d2.value) + } + + baseExp := min(d.exp, d2.exp) + rd := d.rescale(baseExp) + rd2 := d2.rescale(baseExp) + + return rd.value.Cmp(rd2.value) +} + +// Equals returns whether the numbers represented by d and d2 are equal. +func (d Decimal) Equals(d2 Decimal) bool { + return d.Cmp(d2) == 0 +} + +// Exponent returns the exponent, or scale component of the decimal. +func (d Decimal) Exponent() int32 { + return d.exp +} + +// IntPart returns the integer component of the decimal. +func (d Decimal) IntPart() int64 { + scaledD := d.rescale(0) + return scaledD.value.Int64() +} + +// Rat returns a rational number representation of the decimal. +func (d Decimal) Rat() *big.Rat { + d.ensureInitialized() + if d.exp <= 0 { + // NOTE(vadim): must negate after casting to prevent int32 overflow + denom := new(big.Int).Exp(tenInt, big.NewInt(-int64(d.exp)), nil) + return new(big.Rat).SetFrac(d.value, denom) + } else { + mul := new(big.Int).Exp(tenInt, big.NewInt(int64(d.exp)), nil) + num := new(big.Int).Mul(d.value, mul) + return new(big.Rat).SetFrac(num, oneInt) + } +} + +// Float64 returns the nearest float64 value for d and a bool indicating +// whether f represents d exactly. +// For more details, see the documentation for big.Rat.Float64 +func (d Decimal) Float64() (f float64, exact bool) { + return d.Rat().Float64() +} + +// String returns the string representation of the decimal +// with the fixed point. +// +// Example: +// +// d := New(-12345, -3) +// println(d.String()) +// +// Output: +// +// -12.345 +// +func (d Decimal) String() string { + return d.string(true) +} + +// StringFixed returns a rounded fixed-point string with places digits after +// the decimal point. +// +// Example: +// +// NewFromFloat(0).StringFixed(2) // output: "0.00" +// NewFromFloat(0).StringFixed(0) // output: "0" +// NewFromFloat(5.45).StringFixed(0) // output: "5" +// NewFromFloat(5.45).StringFixed(1) // output: "5.5" +// NewFromFloat(5.45).StringFixed(2) // output: "5.45" +// NewFromFloat(5.45).StringFixed(3) // output: "5.450" +// NewFromFloat(545).StringFixed(-1) // output: "550" +// +func (d Decimal) StringFixed(places int32) string { + rounded := d.Round(places) + return rounded.string(false) +} + +// Round rounds the decimal to places decimal places. +// If places < 0, it will round the integer part to the nearest 10^(-places). +// +// Example: +// +// NewFromFloat(5.45).Round(1).String() // output: "5.5" +// NewFromFloat(545).Round(-1).String() // output: "550" +// +func (d Decimal) Round(places int32) Decimal { + // truncate to places + 1 + ret := d.rescale(-places - 1) + + // add sign(d) * 0.5 + if ret.value.Sign() < 0 { + ret.value.Sub(ret.value, fiveInt) + } else { + ret.value.Add(ret.value, fiveInt) + } + + // floor for positive numbers, ceil for negative numbers + _, m := ret.value.DivMod(ret.value, tenInt, new(big.Int)) + ret.exp += 1 + if ret.value.Sign() < 0 && m.Cmp(zeroInt) != 0 { + ret.value.Add(ret.value, oneInt) + } + + return ret +} + +// Floor returns the nearest integer value less than or equal to d. +func (d Decimal) Floor() Decimal { + d.ensureInitialized() + + exp := big.NewInt(10) + + // NOTE(vadim): must negate after casting to prevent int32 overflow + exp.Exp(exp, big.NewInt(-int64(d.exp)), nil) + + z := new(big.Int).Div(d.value, exp) + return Decimal{value: z, exp: 0} +} + +// Ceil returns the nearest integer value greater than or equal to d. +func (d Decimal) Ceil() Decimal { + d.ensureInitialized() + + exp := big.NewInt(10) + + // NOTE(vadim): must negate after casting to prevent int32 overflow + exp.Exp(exp, big.NewInt(-int64(d.exp)), nil) + + z, m := new(big.Int).DivMod(d.value, exp, new(big.Int)) + if m.Cmp(zeroInt) != 0 { + z.Add(z, oneInt) + } + return Decimal{value: z, exp: 0} +} + +// Truncate truncates off digits from the number, without rounding. +// +// NOTE: precision is the last digit that will not be truncated (must be >= 0). +// +// Example: +// +// decimal.NewFromString("123.456").Truncate(2).String() // "123.45" +// +func (d Decimal) Truncate(precision int32) Decimal { + d.ensureInitialized() + if precision >= 0 && -precision > d.exp { + return d.rescale(-precision) + } + return d +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error { + str, err := unquoteIfQuoted(decimalBytes) + if err != nil { + return fmt.Errorf("Error decoding string '%s': %s", decimalBytes, err) + } + + decimal, err := NewFromString(str) + *d = decimal + if err != nil { + return fmt.Errorf("Error decoding string '%s': %s", str, err) + } + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (d Decimal) MarshalJSON() ([]byte, error) { + str := "\"" + d.String() + "\"" + return []byte(str), nil +} + +// Scan implements the sql.Scanner interface for database deserialization. +func (d *Decimal) Scan(value interface{}) error { + // first try to see if the data is stored in database as a Numeric datatype + switch v := value.(type) { + + case float64: + // numeric in sqlite3 sends us float64 + *d = NewFromFloat(v) + return nil + + case int64: + // at least in sqlite3 when the value is 0 in db, the data is sent + // to us as an int64 instead of a float64 ... + *d = New(v, 0) + return nil + + default: + // default is trying to interpret value stored as string + str, err := unquoteIfQuoted(v) + if err != nil { + return err + } + *d, err = NewFromString(str) + return err + } +} + +// Value implements the driver.Valuer interface for database serialization. +func (d Decimal) Value() (driver.Value, error) { + return d.String(), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for XML +// deserialization. +func (d *Decimal) UnmarshalText(text []byte) error { + str := string(text) + + dec, err := NewFromString(str) + *d = dec + if err != nil { + return fmt.Errorf("Error decoding string '%s': %s", str, err) + } + + return nil +} + +// MarshalText implements the encoding.TextMarshaler interface for XML +// serialization. +func (d Decimal) MarshalText() (text []byte, err error) { + return []byte(d.String()), nil +} + +// NOTE: buggy, unintuitive, and DEPRECATED! Use StringFixed instead. +// StringScaled first scales the decimal then calls .String() on it. +func (d Decimal) StringScaled(exp int32) string { + return d.rescale(exp).String() +} + +func (d Decimal) string(trimTrailingZeros bool) string { + if d.exp >= 0 { + return d.rescale(0).value.String() + } + + abs := new(big.Int).Abs(d.value) + str := abs.String() + + var intPart, fractionalPart string + + // NOTE(vadim): this cast to int will cause bugs if d.exp == INT_MIN + // and you are on a 32-bit machine. Won't fix this super-edge case. + dExpInt := int(d.exp) + if len(str) > -dExpInt { + intPart = str[:len(str)+dExpInt] + fractionalPart = str[len(str)+dExpInt:] + } else { + intPart = "0" + + num0s := -dExpInt - len(str) + fractionalPart = strings.Repeat("0", num0s) + str + } + + if trimTrailingZeros { + i := len(fractionalPart) - 1 + for ; i >= 0; i-- { + if fractionalPart[i] != '0' { + break + } + } + fractionalPart = fractionalPart[:i+1] + } + + number := intPart + if len(fractionalPart) > 0 { + number += "." + fractionalPart + } + + if d.value.Sign() < 0 { + return "-" + number + } + + return number +} + +func (d *Decimal) ensureInitialized() { + if d.value == nil { + d.value = new(big.Int) + } +} + +// Returns the smallest Decimal that was passed in the arguments. +// +// To call this function with an array, you must do: +// +// Min(arr[0], arr[1:]...) +// +// This makes it harder to accidentally call Min with 0 arguments. +func Min(first Decimal, rest ...Decimal) Decimal { + ans := first + for _, item := range rest { + if item.Cmp(ans) < 0 { + ans = item + } + } + return ans +} + +// Returns the largest Decimal that was passed in the arguments. +// +// To call this function with an array, you must do: +// +// Max(arr[0], arr[1:]...) +// +// This makes it harder to accidentally call Max with 0 arguments. +func Max(first Decimal, rest ...Decimal) Decimal { + ans := first + for _, item := range rest { + if item.Cmp(ans) > 0 { + ans = item + } + } + return ans +} + +func min(x, y int32) int32 { + if x >= y { + return y + } + return x +} + +func round(n float64) int64 { + if n < 0 { + return int64(n - 0.5) + } + return int64(n + 0.5) +} + +func unquoteIfQuoted(value interface{}) (string, error) { + bytes, ok := value.([]byte) + if !ok { + return "", fmt.Errorf("Could not convert value '%+v' to byte array", + value) + } + + // If the amount is quoted, strip the quotes + if len(bytes) > 2 && bytes[0] == '"' && bytes[len(bytes)-1] == '"' { + bytes = bytes[1 : len(bytes)-1] + } + return string(bytes), nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index c3df6d53f..9b6671372 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -20,6 +20,12 @@ "revision": "792786c7400a136282c1664665ae0a8db921c6c2", "revisionTime": "2016-01-10T10:55:54Z" }, + { + "checksumSHA1": "/o4h+zapTyKGjeZqcNhUWeikF0M=", + "path": "github.com/shopspring/decimal", + "revision": "74d668a796fa3734c552fb907d9b83146d0bebee", + "revisionTime": "2016-03-11T12:45:26Z" + }, { "checksumSHA1": "K0crHygPTP42i1nLKWphSlvOQJw=", "path": "github.com/stretchr/objx",