mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 20:40:48 +01:00
Added Banker Rounding (#61)
* made GTE/GT/LT/LTE go-lint compliant * added banker rounding and tests (RoundBank, StringFixedBank)
This commit is contained in:
parent
730de27aa5
commit
aed1bfe463
2 changed files with 137 additions and 4 deletions
57
decimal.go
57
decimal.go
|
@ -431,23 +431,23 @@ func (d Decimal) Equals(d2 Decimal) bool {
|
||||||
return d.Equal(d2)
|
return d.Equal(d2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Greater Than (GT) returns true when d is greater than d2.
|
// GreaterThan (GT) returns true when d is greater than d2.
|
||||||
func (d Decimal) GreaterThan(d2 Decimal) bool {
|
func (d Decimal) GreaterThan(d2 Decimal) bool {
|
||||||
return d.Cmp(d2) == 1
|
return d.Cmp(d2) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Greater Than or Equal (GTE) returns true when d is greater than or equal to d2.
|
// GreaterThanOrEqual (GTE) returns true when d is greater than or equal to d2.
|
||||||
func (d Decimal) GreaterThanOrEqual(d2 Decimal) bool {
|
func (d Decimal) GreaterThanOrEqual(d2 Decimal) bool {
|
||||||
cmp := d.Cmp(d2)
|
cmp := d.Cmp(d2)
|
||||||
return cmp == 1 || cmp == 0
|
return cmp == 1 || cmp == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less Than (LT) returns true when d is less than d2.
|
// LessThan (LT) returns true when d is less than d2.
|
||||||
func (d Decimal) LessThan(d2 Decimal) bool {
|
func (d Decimal) LessThan(d2 Decimal) bool {
|
||||||
return d.Cmp(d2) == -1
|
return d.Cmp(d2) == -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less Than or Equal (LTE) returns true when d is less than or equal to d2.
|
// LessThanOrEqual (LTE) returns true when d is less than or equal to d2.
|
||||||
func (d Decimal) LessThanOrEqual(d2 Decimal) bool {
|
func (d Decimal) LessThanOrEqual(d2 Decimal) bool {
|
||||||
cmp := d.Cmp(d2)
|
cmp := d.Cmp(d2)
|
||||||
return cmp == -1 || cmp == 0
|
return cmp == -1 || cmp == 0
|
||||||
|
@ -539,6 +539,24 @@ func (d Decimal) StringFixed(places int32) string {
|
||||||
return rounded.string(false)
|
return rounded.string(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringFixedBank returns a banker 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.4"
|
||||||
|
// 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) StringFixedBank(places int32) string {
|
||||||
|
rounded := d.RoundBank(places)
|
||||||
|
return rounded.string(false)
|
||||||
|
}
|
||||||
|
|
||||||
// Round rounds the decimal to places decimal places.
|
// Round rounds the decimal to places decimal places.
|
||||||
// If places < 0, it will round the integer part to the nearest 10^(-places).
|
// If places < 0, it will round the integer part to the nearest 10^(-places).
|
||||||
//
|
//
|
||||||
|
@ -568,6 +586,37 @@ func (d Decimal) Round(places int32) Decimal {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundBank rounds the decimal to places decimal places.
|
||||||
|
// If the final digit to round is equidistant from the nearest two integers the
|
||||||
|
// rounded value is taken as the even number
|
||||||
|
//
|
||||||
|
// If places < 0, it will round the integer part to the nearest 10^(-places).
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
//
|
||||||
|
// NewFromFloat(5.45).Round(1).String() // output: "5.4"
|
||||||
|
// NewFromFloat(545).Round(-1).String() // output: "540"
|
||||||
|
// NewFromFloat(5.46).Round(1).String() // output: "5.5"
|
||||||
|
// NewFromFloat(546).Round(-1).String() // output: "550"
|
||||||
|
// NewFromFloat(5.55).Round(1).String() // output: "5.6"
|
||||||
|
// NewFromFloat(555).Round(-1).String() // output: "560"
|
||||||
|
//
|
||||||
|
func (d Decimal) RoundBank(places int32) Decimal {
|
||||||
|
|
||||||
|
round := d.Round(places)
|
||||||
|
remainder := d.Sub(round).Abs()
|
||||||
|
|
||||||
|
if remainder.value.Cmp(fiveInt) == 0 && round.value.Bit(0) != 0 {
|
||||||
|
if round.value.Sign() < 0 {
|
||||||
|
round.value.Add(round.value, oneInt)
|
||||||
|
} else {
|
||||||
|
round.value.Sub(round.value, oneInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return round
|
||||||
|
}
|
||||||
|
|
||||||
// Floor returns the nearest integer value less than or equal to d.
|
// Floor returns the nearest integer value less than or equal to d.
|
||||||
func (d Decimal) Floor() Decimal {
|
func (d Decimal) Floor() Decimal {
|
||||||
d.ensureInitialized()
|
d.ensureInitialized()
|
||||||
|
|
|
@ -550,6 +550,90 @@ func TestDecimal_RoundAndStringFixed(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecimal_BankRoundAndStringFixed(t *testing.T) {
|
||||||
|
type testData struct {
|
||||||
|
input string
|
||||||
|
places int32
|
||||||
|
expected string
|
||||||
|
expectedFixed string
|
||||||
|
}
|
||||||
|
tests := []testData{
|
||||||
|
{"1.454", 0, "1", ""},
|
||||||
|
{"1.454", 1, "1.5", ""},
|
||||||
|
{"1.454", 2, "1.45", ""},
|
||||||
|
{"1.454", 3, "1.454", ""},
|
||||||
|
{"1.454", 4, "1.454", "1.4540"},
|
||||||
|
{"1.454", 5, "1.454", "1.45400"},
|
||||||
|
{"1.554", 0, "2", ""},
|
||||||
|
{"1.554", 1, "1.6", ""},
|
||||||
|
{"1.554", 2, "1.55", ""},
|
||||||
|
{"0.554", 0, "1", ""},
|
||||||
|
{"0.454", 0, "0", ""},
|
||||||
|
{"0.454", 5, "0.454", "0.45400"},
|
||||||
|
{"0", 0, "0", ""},
|
||||||
|
{"0", 1, "0", "0.0"},
|
||||||
|
{"0", 2, "0", "0.00"},
|
||||||
|
{"0", -1, "0", ""},
|
||||||
|
{"5", 2, "5", "5.00"},
|
||||||
|
{"5", 1, "5", "5.0"},
|
||||||
|
{"5", 0, "5", ""},
|
||||||
|
{"500", 2, "500", "500.00"},
|
||||||
|
{"545", -2, "500", ""},
|
||||||
|
{"545", -3, "1000", ""},
|
||||||
|
{"545", -4, "0", ""},
|
||||||
|
{"499", -3, "0", ""},
|
||||||
|
{"499", -4, "0", ""},
|
||||||
|
{"1.45", 1, "1.4", ""},
|
||||||
|
{"1.55", 1, "1.6", ""},
|
||||||
|
{"1.65", 1, "1.6", ""},
|
||||||
|
{"545", -1, "540", ""},
|
||||||
|
{"565", -1, "560", ""},
|
||||||
|
{"555", -1, "560", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
// add negative number tests
|
||||||
|
for _, test := range tests {
|
||||||
|
expected := test.expected
|
||||||
|
if expected != "0" {
|
||||||
|
expected = "-" + expected
|
||||||
|
}
|
||||||
|
expectedStr := test.expectedFixed
|
||||||
|
if strings.ContainsAny(expectedStr, "123456789") && expectedStr != "" {
|
||||||
|
expectedStr = "-" + expectedStr
|
||||||
|
}
|
||||||
|
tests = append(tests,
|
||||||
|
testData{"-" + test.input, test.places, expected, expectedStr})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
d, err := NewFromString(test.input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test Round
|
||||||
|
expected, err := NewFromString(test.expected)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
got := d.RoundBank(test.places)
|
||||||
|
if !got.Equal(expected) {
|
||||||
|
t.Errorf("Bank Rounding %s to %d places, got %s, expected %s",
|
||||||
|
d, test.places, got, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test StringFixed
|
||||||
|
if test.expectedFixed == "" {
|
||||||
|
test.expectedFixed = test.expected
|
||||||
|
}
|
||||||
|
gotStr := d.StringFixedBank(test.places)
|
||||||
|
if gotStr != test.expectedFixed {
|
||||||
|
t.Errorf("(%s).StringFixed(%d): got %s, expected %s",
|
||||||
|
d, test.places, gotStr, test.expectedFixed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDecimal_Uninitialized(t *testing.T) {
|
func TestDecimal_Uninitialized(t *testing.T) {
|
||||||
a := Decimal{}
|
a := Decimal{}
|
||||||
b := Decimal{}
|
b := Decimal{}
|
||||||
|
|
Loading…
Reference in a new issue