commit 3bac4bd1662190f1b37f23dc23b7d9fe79ca977a Author: Vadim Graboys Date: Tue Feb 10 15:33:58 2015 -0500 init diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ad2148a --- /dev/null +++ b/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/README.md b/README.md new file mode 100644 index 0000000..d3c47bc --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# decimal + +Arbitrary-precision fixed-point decimal numbers in go. + +NOTE: can "only" represent numbers with a maximum of 2^31 digits after the decmial point. + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/jellolabs/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) + fmt.Println("Pre-tax:", preTax) + fmt.Println("Taxes:", total.Sub(preTax)) + fmt.Println("Total:", total) + fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) +} +``` + +## Documentation + +http://godoc.org/github.com/jellolabs/decimal + +## License + +The MIT License (MIT) diff --git a/decimal.go b/decimal.go new file mode 100644 index 0000000..35c40c4 --- /dev/null +++ b/decimal.go @@ -0,0 +1,458 @@ +// Package 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" +) + +// number of decimal places in the result when it doesn't divide exactly +// ex: 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 + +// to make computations faster +var Zero = New(0, 1) +var tenInt = big.NewInt(10) +var oneInt = big.NewInt(1) + +// 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 +func New(value int64, exp int32) Decimal { + return Decimal{ + value: big.NewInt(value), + exp: exp, + } +} + +// Returns a new Decimal from a string representation +// +// Example: +// +// d, err := NewFromString("-123.45") +// d2, err := NewFromString(".0001") +// +func NewFromString(value string) (Decimal, error) { + var intString string + var exp int32 + 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 + exp = 0 + } else if len(parts) == 2 { + intString = parts[0] + parts[1] + expInt := -len(parts[1]) + if expInt < math.MinInt32 { + // 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", value) + } + exp = int32(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) + } + + return Decimal{ + value: dValue, + exp: exp, + }, nil +} + +// Converts a float64 to Decimal +// +// ex: NewFromFloat(123.45678901234567).String() // output: "123.4567890123456" +// ex: 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 && !math.IsInf(value, 0) { + 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 +} + +// Same as NewFromFloat, except you can choose the number of fractional digits +// +// ex: 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, + } +} + +func (d Decimal) Abs() Decimal { + d.ensureInitialized() + d2Value := new(big.Int).Abs(d.value) + return Decimal{ + value: d2Value, + exp: d.exp, + } +} + +// Add adds d to d2 and return d3 +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 subtracts d2 from d and returns d3 +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, + } +} + +// returns d multiplied with 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), + } +} + +// returns d divided by d2 +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 +} + +// Cmp compares x and y and returns -1, 0 or 1 +// +// Example +// +//-1 if x < y +// 0 if x == y +//+1 if x > y +// +func (d Decimal) Cmp(d2 Decimal) int { + baseExp := min(d.exp, d2.exp) + rd := d.rescale(baseExp) + rd2 := d2.rescale(baseExp) + + return rd.value.Cmp(rd2.value) +} + +func (d Decimal) Equals(d2 Decimal) bool { + return d.Cmp(d2) == 0 +} + +func (d Decimal) Exponent() int32 { + return d.exp +} + +func (d Decimal) IntPart() int64 { + scaledD := d.rescale(0) + return scaledD.value.Int64() +} + +// Returns a rational number representation of the decimal +func (d Decimal) Rat() *big.Rat { + d.ensureInitialized() + if d.exp <= 0 { + 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) + } +} + +// 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 { + if d.exp >= 0 { + return d.rescale(0).value.String() + } + + abs := new(big.Int).Abs(d.value) + str := abs.String() + + var intPart, fractionalPart string + 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 + } + + 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 +} + +// 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) 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 +} + +func (d Decimal) MarshalJSON() ([]byte, error) { + str := "\"" + d.String() + "\"" + return []byte(str), nil +} + +// This truncates off digits from the number +// precision is >= 0, the last digit that will not be truncated (in 10^x format) +// ex: 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 +} + +// db mapping methods +func (d *Decimal) Scan(value interface{}) error { + str, err := unquoteIfQuoted(value) + if err != nil { + return err + } + *d, err = NewFromString(str) + + return err +} + +func (d Decimal) Value() (driver.Value, error) { + return d.String(), nil +} + +// xml serialization/deserialization methods +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 +} + +func (d Decimal) MarshalText() (text []byte, err error) { + return []byte(d.String()), nil +} + +func (d *Decimal) ensureInitialized() { + if d.value == nil { + d.value = new(big.Int) + } +} + +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/decimal_test.go b/decimal_test.go new file mode 100644 index 0000000..e4f82fb --- /dev/null +++ b/decimal_test.go @@ -0,0 +1,469 @@ +package decimal + +import ( + "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", +} + +func TestNewFromFloat(t *testing.T) { + // add negatives + for f, s := range testTable { + if f > 0 { + testTable[-f] = "-" + s + } + } + + 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) { + // add negatives + for f, s := range testTable { + if f > 0 { + testTable[-f] = "-" + s + } + } + + 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()) + } + } +} + +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") + } +} + +// 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") + } +} + +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 + +}