Fix Floor() and Ceil() for integer values (#64)

Implementation of aforementioned methods applied an integer multiplier
to int part, and rejected the exponent. This did not work well for
positive exponent values - multiplier was supposed to be a non-integer
value less than 1, but was rounded up to it. This caused different
results for equal Decimal values like decimal.New(19, 1) and
decimal.New(1900, -1).

Now functions return the receiver if it represents an integer value.
This also reduces execution time for previously broken cases.
This commit is contained in:
tbrs 2017-11-09 01:52:54 +03:00 committed by Victor Quinn
parent faaed5fca7
commit 9ca7f51822
2 changed files with 96 additions and 16 deletions

View file

@ -682,6 +682,10 @@ func (d Decimal) RoundCash(interval uint8) Decimal {
func (d Decimal) Floor() Decimal { func (d Decimal) Floor() Decimal {
d.ensureInitialized() d.ensureInitialized()
if d.exp >= 0 {
return d
}
exp := big.NewInt(10) exp := big.NewInt(10)
// NOTE(vadim): must negate after casting to prevent int32 overflow // NOTE(vadim): must negate after casting to prevent int32 overflow
@ -695,6 +699,10 @@ func (d Decimal) Floor() Decimal {
func (d Decimal) Ceil() Decimal { func (d Decimal) Ceil() Decimal {
d.ensureInitialized() d.ensureInitialized()
if d.exp >= 0 {
return d
}
exp := big.NewInt(10) exp := big.NewInt(10)
// NOTE(vadim): must negate after casting to prevent int32 overflow // NOTE(vadim): must negate after casting to prevent int32 overflow

View file

@ -508,11 +508,17 @@ func TestDecimal_rescale(t *testing.T) {
} }
func TestDecimal_Floor(t *testing.T) { func TestDecimal_Floor(t *testing.T) {
type testData struct { assertFloor := func(input, expected Decimal) {
got := input.Floor()
if !got.Equal(expected) {
t.Errorf("Floor(%s): got %s, expected %s", input, got, expected)
}
}
type testDataString struct {
input string input string
expected string expected string
} }
tests := []testData{ testsWithStrings := []testDataString{
{"1.999", "1"}, {"1.999", "1"},
{"1", "1"}, {"1", "1"},
{"1.01", "1"}, {"1.01", "1"},
@ -525,22 +531,66 @@ func TestDecimal_Floor(t *testing.T) {
{"-1.01", "-2"}, {"-1.01", "-2"},
{"-1.999", "-2"}, {"-1.999", "-2"},
} }
for _, test := range tests { for _, test := range testsWithStrings {
d, _ := NewFromString(test.input)
expected, _ := NewFromString(test.expected) expected, _ := NewFromString(test.expected)
got := d.Floor() input, _ := NewFromString(test.input)
if !got.Equal(expected) { assertFloor(input, expected)
t.Errorf("Floor(%s): got %s, expected %s", d, got, expected)
} }
type testDataDecimal struct {
input Decimal
expected string
}
testsWithDecimals := []testDataDecimal{
{New(100, -1), "10"},
{New(10, 0), "10"},
{New(1, 1), "10"},
{New(1999, -3), "1"},
{New(101, -2), "1"},
{New(1, 0), "1"},
{New(0, 0), "0"},
{New(9, -1), "0"},
{New(1, -1), "0"},
{New(-1, -1), "-1"},
{New(-9, -1), "-1"},
{New(-1, 0), "-1"},
{New(-101, -2), "-2"},
{New(-1999, -3), "-2"},
}
for _, test := range testsWithDecimals {
expected, _ := NewFromString(test.expected)
assertFloor(test.input, expected)
}
}
func Benchmark_FloorFast(b *testing.B) {
input := New(200, 2)
b.ResetTimer()
for i := 0; i < b.N; i++ {
input.Floor()
}
}
func Benchmark_FloorRegular(b *testing.B) {
input := New(200, -2)
b.ResetTimer()
for i := 0; i < b.N; i++ {
input.Floor()
} }
} }
func TestDecimal_Ceil(t *testing.T) { func TestDecimal_Ceil(t *testing.T) {
type testData struct { assertCeil := func(input, expected Decimal) {
got := input.Ceil()
if !got.Equal(expected) {
t.Errorf("Ceil(%s): got %s, expected %s", input, got, expected)
}
}
type testDataString struct {
input string input string
expected string expected string
} }
tests := []testData{ testsWithStrings := []testDataString{
{"1.999", "2"}, {"1.999", "2"},
{"1", "1"}, {"1", "1"},
{"1.01", "2"}, {"1.01", "2"},
@ -553,13 +603,35 @@ func TestDecimal_Ceil(t *testing.T) {
{"-1.01", "-1"}, {"-1.01", "-1"},
{"-1.999", "-1"}, {"-1.999", "-1"},
} }
for _, test := range tests { for _, test := range testsWithStrings {
d, _ := NewFromString(test.input)
expected, _ := NewFromString(test.expected) expected, _ := NewFromString(test.expected)
got := d.Ceil() input, _ := NewFromString(test.input)
if !got.Equal(expected) { assertCeil(input, expected)
t.Errorf("Ceil(%s): got %s, expected %s", d, got, expected)
} }
type testDataDecimal struct {
input Decimal
expected string
}
testsWithDecimals := []testDataDecimal{
{New(100, -1), "10"},
{New(10, 0), "10"},
{New(1, 1), "10"},
{New(1999, -3), "2"},
{New(101, -2), "2"},
{New(1, 0), "1"},
{New(0, 0), "0"},
{New(9, -1), "1"},
{New(1, -1), "1"},
{New(-1, -1), "0"},
{New(-9, -1), "0"},
{New(-1, 0), "-1"},
{New(-101, -2), "-1"},
{New(-1999, -3), "-1"},
}
for _, test := range testsWithDecimals {
expected, _ := NewFromString(test.expected)
assertCeil(test.input, expected)
} }
} }