This commit is contained in:
Vadim Graboys 2015-02-10 15:33:58 -05:00
commit 3bac4bd166
4 changed files with 1020 additions and 0 deletions

45
LICENSE Normal file
View file

@ -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.
"""

48
README.md Normal file
View file

@ -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)

458
decimal.go Normal file
View file

@ -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
}

469
decimal_test.go Normal file
View file

@ -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
}