From 8bd41ac55db38647a900721e3ff8dcbc0084e5a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Wo=C5=9B?= Date: Fri, 11 Dec 2020 19:41:11 +0100 Subject: [PATCH] Implement RoundUp and RoundDown methods (#196) * Implement RoundUp and RoundDown methods --- decimal.go | 42 +++++++++++ decimal_test.go | 182 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 222 insertions(+), 2 deletions(-) diff --git a/decimal.go b/decimal.go index c0404f1d..75f845ef 100644 --- a/decimal.go +++ b/decimal.go @@ -870,6 +870,48 @@ func (d Decimal) Round(places int32) Decimal { return ret } +// RoundUp rounds the decimal towards +infinity. +// +// Example: +// +// NewFromFloat(545).RoundUp(-2).String() // output: "600" +// NewFromFloat(1.1001).RoundUp(2).String() // output: "1.11" +// NewFromFloat(-1.454).RoundUp(1).String() // output: "-1.4" +// +func (d Decimal) RoundUp(places int32) Decimal { + if d.exp >= -places { + return d + } + + rescaled := d.rescale(-places) + if d.value.Sign() > 0 { + rescaled.value.Add(rescaled.value, oneInt) + } + + return rescaled +} + +// RoundDown rounds the decimal towards -infinity. +// +// Example: +// +// NewFromFloat(545).RoundDown(-2).String() // output: "500" +// NewFromFloat(1.1001).RoundDown(2).String() // output: "1.1" +// NewFromFloat(-1.454).RoundDown(1).String() // output: "-1.5" +// +func (d Decimal) RoundDown(places int32) Decimal { + if d.exp >= -places { + return d + } + + rescaled := d.rescale(-places) + if d.value.Sign() < 0 { + rescaled.value.Sub(rescaled.value, oneInt) + } + + return rescaled +} + // 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 diff --git a/decimal_test.go b/decimal_test.go index d7ec5e42..7d4a38f1 100644 --- a/decimal_test.go +++ b/decimal_test.go @@ -968,13 +968,13 @@ func TestDecimal_RoundAndStringFixed(t *testing.T) { for _, test := range tests { d, err := NewFromString(test.input) if err != nil { - panic(err) + t.Fatal(err) } // test Round expected, err := NewFromString(test.expected) if err != nil { - panic(err) + t.Fatal(err) } got := d.Round(test.places) if !got.Equal(expected) { @@ -994,6 +994,184 @@ func TestDecimal_RoundAndStringFixed(t *testing.T) { } } +func TestDecimal_RoundUpAndStringFixed(t *testing.T) { + type testData struct { + input string + places int32 + expected string + expectedFixed string + } + tests := []testData{ + {"1.454", 0, "2", ""}, + {"1.454", 1, "1.5", ""}, + {"1.454", 2, "1.46", ""}, + {"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.56", ""}, + {"0.554", 0, "1", ""}, + {"0.454", 0, "1", ""}, + {"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", -1, "550", ""}, + {"545", -2, "600", ""}, + {"545", -3, "1000", ""}, + {"545", -4, "10000", ""}, + {"499", -3, "1000", ""}, + {"499", -4, "10000", ""}, + {"1.1001", 2, "1.11", ""}, + {"-1.1001", 2, "-1.10", ""}, + {"-1.454", 0, "-1", ""}, + {"-1.454", 1, "-1.4", ""}, + {"-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, "-1", ""}, + {"-1.554", 1, "-1.5", ""}, + {"-1.554", 2, "-1.55", ""}, + {"-0.554", 0, "0", ""}, + {"-0.454", 0, "0", ""}, + {"-0.454", 5, "-0.454", "-0.45400"}, + {"-5", 2, "-5", "-5.00"}, + {"-5", 1, "-5", "-5.0"}, + {"-5", 0, "-5", ""}, + {"-500", 2, "-500", "-500.00"}, + {"-545", -1, "-540", ""}, + {"-545", -2, "-500", ""}, + {"-545", -3, "0", ""}, + {"-545", -4, "0", ""}, + {"-499", -3, "0", ""}, + {"-499", -4, "0", ""}, + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } + + // test Round + expected, err := NewFromString(test.expected) + if err != nil { + t.Fatal(err) + } + got := d.RoundUp(test.places) + if !got.Equal(expected) { + t.Errorf("Rounding up %s to %d places, got %s, expected %s", + d, test.places, got, expected) + } + + // test StringFixed + if test.expectedFixed == "" { + test.expectedFixed = test.expected + } + gotStr := got.StringFixed(test.places) + if gotStr != test.expectedFixed { + t.Errorf("(%s).StringFixed(%d): got %s, expected %s", + d, test.places, gotStr, test.expectedFixed) + } + } +} + +func TestDecimal_RoundDownAndStringFixed(t *testing.T) { + type testData struct { + input string + places int32 + expected string + expectedFixed string + } + tests := []testData{ + {"1.454", 0, "1", ""}, + {"1.454", 1, "1.4", ""}, + {"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, "1", ""}, + {"1.554", 1, "1.5", ""}, + {"1.554", 2, "1.55", ""}, + {"0.554", 0, "0", ""}, + {"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", -1, "540", ""}, + {"545", -2, "500", ""}, + {"545", -3, "0", ""}, + {"545", -4, "0", ""}, + {"499", -3, "0", ""}, + {"499", -4, "0", ""}, + {"1.1001", 2, "1.10", ""}, + {"-1.1001", 2, "-1.11", ""}, + {"-1.454", 0, "-2", ""}, + {"-1.454", 1, "-1.5", ""}, + {"-1.454", 2, "-1.46", ""}, + {"-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.56", ""}, + {"-0.554", 0, "-1", ""}, + {"-0.454", 0, "-1", ""}, + {"-0.454", 5, "-0.454", "-0.45400"}, + {"-5", 2, "-5", "-5.00"}, + {"-5", 1, "-5", "-5.0"}, + {"-5", 0, "-5", ""}, + {"-500", 2, "-500", "-500.00"}, + {"-545", -1, "-550", ""}, + {"-545", -2, "-600", ""}, + {"-545", -3, "-1000", ""}, + {"-545", -4, "-10000", ""}, + {"-499", -3, "-1000", ""}, + {"-499", -4, "-10000", ""}, + } + + for _, test := range tests { + d, err := NewFromString(test.input) + if err != nil { + t.Fatal(err) + } + + // test Round + expected, err := NewFromString(test.expected) + if err != nil { + t.Fatal(err) + } + got := d.RoundDown(test.places) + if !got.Equal(expected) { + t.Errorf("Rounding down %s to %d places, got %s, expected %s", + d, test.places, got, expected) + } + + // test StringFixed + if test.expectedFixed == "" { + test.expectedFixed = test.expected + } + gotStr := got.StringFixed(test.places) + if gotStr != test.expectedFixed { + t.Errorf("(%s).StringFixed(%d): got %s, expected %s", + d, test.places, gotStr, test.expectedFixed) + } + } +} + func TestDecimal_BankRoundAndStringFixed(t *testing.T) { type testData struct { input string