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 {
d.ensureInitialized()
if d.exp >= 0 {
return d
}
exp := big.NewInt(10)
// NOTE(vadim): must negate after casting to prevent int32 overflow
@ -695,6 +699,10 @@ func (d Decimal) Floor() Decimal {
func (d Decimal) Ceil() Decimal {
d.ensureInitialized()
if d.exp >= 0 {
return d
}
exp := big.NewInt(10)
// 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) {
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
expected string
}
tests := []testData{
testsWithStrings := []testDataString{
{"1.999", "1"},
{"1", "1"},
{"1.01", "1"},
@ -525,22 +531,66 @@ func TestDecimal_Floor(t *testing.T) {
{"-1.01", "-2"},
{"-1.999", "-2"},
}
for _, test := range tests {
d, _ := NewFromString(test.input)
for _, test := range testsWithStrings {
expected, _ := NewFromString(test.expected)
got := d.Floor()
if !got.Equal(expected) {
t.Errorf("Floor(%s): got %s, expected %s", d, got, expected)
}
input, _ := NewFromString(test.input)
assertFloor(input, 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) {
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
expected string
}
tests := []testData{
testsWithStrings := []testDataString{
{"1.999", "2"},
{"1", "1"},
{"1.01", "2"},
@ -553,13 +603,35 @@ func TestDecimal_Ceil(t *testing.T) {
{"-1.01", "-1"},
{"-1.999", "-1"},
}
for _, test := range tests {
d, _ := NewFromString(test.input)
for _, test := range testsWithStrings {
expected, _ := NewFromString(test.expected)
got := d.Ceil()
if !got.Equal(expected) {
t.Errorf("Ceil(%s): got %s, expected %s", d, got, expected)
}
input, _ := NewFromString(test.input)
assertCeil(input, 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)
}
}