mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 12:30:49 +01:00
init
This commit is contained in:
commit
3bac4bd166
4 changed files with 1020 additions and 0 deletions
45
LICENSE
Normal file
45
LICENSE
Normal 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
48
README.md
Normal 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
458
decimal.go
Normal 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
469
decimal_test.go
Normal 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
|
||||
|
||||
}
|
Loading…
Reference in a new issue