From f724e87efc9a7fa63e10e8295425ea2d7ee39bc6 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Mon, 16 Aug 2021 23:15:02 +0800 Subject: [PATCH 01/12] add tests --- expression/builtin_math_test.go | 5 +++++ expression/integration_test.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/expression/builtin_math_test.go b/expression/builtin_math_test.go index cd5983170bceb..974bd83c0dd1d 100644 --- a/expression/builtin_math_test.go +++ b/expression/builtin_math_test.go @@ -435,6 +435,11 @@ func (s *testEvaluatorSuite) TestRound(c *C) { {[]interface{}{-1.5, 0}, -2}, {[]interface{}{1.5, 0}, 2}, {[]interface{}{23.298, -1}, 20}, + {[]interface{}{49.99999, -2}, 0}, + {[]interface{}{50, -2}, 100}, + {[]interface{}{50.00001, -2}, 100}, + {[]interface{}{123456789, -5}, 123500000}, + {[]interface{}{2146213728964879326, -15}, 2146000000000000000}, {[]interface{}{newDec("-1.23")}, newDec("-1")}, {[]interface{}{newDec("-1.23"), 1}, newDec("-1.2")}, {[]interface{}{newDec("-1.58")}, newDec("-2")}, diff --git a/expression/integration_test.go b/expression/integration_test.go index 2efcae665713c..130fd161aa374 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -696,6 +696,15 @@ func (s *testIntegrationSuite2) TestMathBuiltin(c *C) { result.Check(testkit.Rows("100000000000000 1000000000000000 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")) result = tk.MustQuery("SELECT ROUND(1e-14, 1), ROUND(1e-15, 1), ROUND(1e-308, 1)") result.Check(testkit.Rows("0 0 0")) + result = tk.MustQuery("SELECT round(49.99999, -2), round(50, -2), round(50.00001, -2)") + result.Check(testkit.Rows("0 100 100")) + result = tk.MustQuery("SELECT round(123456789, -5), round(2146213728964879326, -15) ") + result.Check(testkit.Rows("123500000 2146000000000000000")) + // MySQL 8 will report an error if round result overflows + rs, err = tk.Exec("select round(18446744073709551615, -19);") + c.Assert(err, NotNil) + terr = errors.Cause(err).(*terror.Error) + c.Assert(terr.Code(), Equals, errors.ErrCode(mysql.ErrDataOutOfRange)) // for truncate result = tk.MustQuery("SELECT truncate(123, -2), truncate(123, 2), truncate(123, 1), truncate(123, -1);") From 9d67a06d679010b0ba68df2acc64f2220326f2b4 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 22:30:01 +0800 Subject: [PATCH 02/12] add RoundInt --- types/helper.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/types/helper.go b/types/helper.go index 2da6bd7275d5f..ceff3a5f1685c 100644 --- a/types/helper.go +++ b/types/helper.go @@ -24,8 +24,7 @@ import ( ) // RoundFloat rounds float val to the nearest even integer value with float64 format, like MySQL Round function. -// RoundFloat uses default rounding mode, see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html -// so rounding use "round to nearest even". +// see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html // e.g, 1.5 -> 2, -1.5 -> -2. func RoundFloat(f float64) float64 { return math.RoundToEven(f) @@ -48,6 +47,27 @@ func Round(f float64, dec int) float64 { return result } +// RoundInt rounds the argument i to dec decimal places. +// dec defaults to 0 if not specified. dec can be negative +// to cause dec digits left of the decimal point of the +// value f to become zero. +func RoundInt(i int64, dec int) int64 { + // is itself when dec >= 0 + if dec >= 0 { + return i + } + // The integer part of a fraction + shift := math.Pow10(dec) + intPart := math.Round(float64(i) * shift) + r := int64(intPart) + // the zero part + for i := 1; i <= -dec; i++ { + r *= 10 + } + return r +} + + // Truncate truncates the argument f to dec decimal places. // dec defaults to 0 if not specified. dec can be negative // to cause dec digits left of the decimal point of the From 774878f06584afd78e49c16957b4c7ffb8ee91ba Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:03:19 +0800 Subject: [PATCH 03/12] migrate types/helper_test.go to testify --- types/helper_test.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/types/helper_test.go b/types/helper_test.go index 6523f7af65cb9..661a2db2691d1 100644 --- a/types/helper_test.go +++ b/types/helper_test.go @@ -16,18 +16,14 @@ package types import ( "strconv" + "testing" - . "github.com/pingcap/check" "github.com/pingcap/errors" + "github.com/stretchr/testify/require" ) -var _ = Suite(&testTypeHelperSuite{}) - -type testTypeHelperSuite struct { -} - -func (s *testTypeHelperSuite) TestStrToInt(c *C) { - c.Parallel() +func TestStrToInt(t *testing.T) { + t.Parallel() tests := []struct { input string output string @@ -42,13 +38,13 @@ func (s *testTypeHelperSuite) TestStrToInt(c *C) { } for _, tt := range tests { output, err := strToInt(tt.input) - c.Assert(errors.Cause(err), Equals, tt.err) - c.Check(strconv.FormatInt(output, 10), Equals, tt.output) + require.Equal(t, tt.err, errors.Cause(err)) + require.Equal(t, tt.output, strconv.FormatInt(output, 10)) } } -func (s *testTypeHelperSuite) TestTruncate(c *C) { - c.Parallel() +func TestTruncate(t *testing.T) { + t.Parallel() tests := []struct { f float64 dec int @@ -61,12 +57,12 @@ func (s *testTypeHelperSuite) TestTruncate(c *C) { } for _, tt := range tests { res := Truncate(tt.f, tt.dec) - c.Assert(res, Equals, tt.expected) + require.Equal(t, tt.expected, res) } } -func (s *testTypeHelperSuite) TestTruncateFloatToString(c *C) { - c.Parallel() +func TestTruncateFloatToString(t *testing.T) { + t.Parallel() tests := []struct { f float64 dec int @@ -83,6 +79,6 @@ func (s *testTypeHelperSuite) TestTruncateFloatToString(c *C) { } for _, tt := range tests { res := TruncateFloatToString(tt.f, tt.dec) - c.Assert(res, Equals, tt.expected) + require.Equal(t, tt.expected, res) } } From 09dce5c445cb82e0acebf94af99fb44cbdbba871 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:09:33 +0800 Subject: [PATCH 04/12] remove old roundfloat function --- types/convert.go | 4 ++-- types/etc_test.go | 23 ----------------------- types/helper.go | 9 +-------- 3 files changed, 3 insertions(+), 33 deletions(-) diff --git a/types/convert.go b/types/convert.go index f6b88597e9964..ff39bb4dfd7a0 100644 --- a/types/convert.go +++ b/types/convert.go @@ -100,7 +100,7 @@ func IntergerSignedLowerBound(intType byte) int64 { // ConvertFloatToInt converts a float64 value to a int value. // `tp` is used in err msg, if there is overflow, this func will report err according to `tp` func ConvertFloatToInt(fval float64, lowerBound, upperBound int64, tp byte) (int64, error) { - val := RoundFloat(fval) + val := math.RoundToEven(fval) if val < float64(lowerBound) { return lowerBound, overflow(val, tp) } @@ -160,7 +160,7 @@ func ConvertUintToUint(val uint64, upperBound uint64, tp byte) (uint64, error) { // ConvertFloatToUint converts a float value to an uint value. func ConvertFloatToUint(sc *stmtctx.StatementContext, fval float64, upperBound uint64, tp byte) (uint64, error) { - val := RoundFloat(fval) + val := math.RoundToEven(fval) if val < 0 { if sc.ShouldClipToZero() { return 0, overflow(val, tp) diff --git a/types/etc_test.go b/types/etc_test.go index c4d8448d7c4e6..66b9a40c55e63 100644 --- a/types/etc_test.go +++ b/types/etc_test.go @@ -132,29 +132,6 @@ func (s *testTypeEtcSuite) TestMaxFloat(c *C) { } } -func (s *testTypeEtcSuite) TestRoundFloat(c *C) { - defer testleak.AfterTest(c)() - tbl := []struct { - Input float64 - Expect float64 - }{ - {2.5, 2}, - {1.5, 2}, - {0.5, 0}, - {0.49999999999999997, 0}, - {0, 0}, - {-0.49999999999999997, 0}, - {-0.5, 0}, - {-2.5, -2}, - {-1.5, -2}, - } - - for _, t := range tbl { - f := RoundFloat(t.Input) - c.Assert(f, Equals, t.Expect) - } -} - func (s *testTypeEtcSuite) TestRound(c *C) { defer testleak.AfterTest(c)() tbl := []struct { diff --git a/types/helper.go b/types/helper.go index ceff3a5f1685c..6f01d3e1b45e5 100644 --- a/types/helper.go +++ b/types/helper.go @@ -23,13 +23,6 @@ import ( "github.com/pingcap/errors" ) -// RoundFloat rounds float val to the nearest even integer value with float64 format, like MySQL Round function. -// see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html -// e.g, 1.5 -> 2, -1.5 -> -2. -func RoundFloat(f float64) float64 { - return math.RoundToEven(f) -} - // Round rounds the argument f to dec decimal places. // dec defaults to 0 if not specified. dec can be negative // to cause dec digits left of the decimal point of the @@ -40,7 +33,7 @@ func Round(f float64, dec int) float64 { if math.IsInf(tmp, 0) { return f } - result := RoundFloat(tmp) / shift + result := math.RoundToEven(tmp) / shift if math.IsNaN(result) { return 0 } From d9f7037906ecd31807a3e2545d5c1c5fc44a740a Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:17:31 +0800 Subject: [PATCH 05/12] add TestRoundInt --- types/helper_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/types/helper_test.go b/types/helper_test.go index 661a2db2691d1..595ccb29cf9f6 100644 --- a/types/helper_test.go +++ b/types/helper_test.go @@ -82,3 +82,27 @@ func TestTruncateFloatToString(t *testing.T) { require.Equal(t, tt.expected, res) } } + +func TestRoundInt(t *testing.T) { + t.Parallel() + tests := []struct { + i int64 + dec int + expected int64 + }{ + {1, 0, 1}, + {2146213728964879326, -15, 2146000000000000000}, + {123456789, -5, 123500000}, + {50, -2, 100}, + {150, 2, 150}, + {-1, 0, -1}, + {-2146213728964879326, -15, -2146000000000000000}, + {-123456789, -5, -123500000}, + {-50, -2, -100}, + {-150, 2, -150}, + } + for _, tt := range tests { + res := RoundInt(tt.i, tt.dec) + require.Equal(t, tt.expected, res) + } +} From d0f31eec5c396e2d6a5f44e3f59d47f410e053b5 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:22:23 +0800 Subject: [PATCH 06/12] update comments --- types/helper.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/types/helper.go b/types/helper.go index 6f01d3e1b45e5..b835b06e49e01 100644 --- a/types/helper.go +++ b/types/helper.go @@ -23,10 +23,9 @@ import ( "github.com/pingcap/errors" ) -// Round rounds the argument f to dec decimal places. +// Round rounds the argument f to dec decimal places using "round to nearest even" rule. // dec defaults to 0 if not specified. dec can be negative -// to cause dec digits left of the decimal point of the -// value f to become zero. +// see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html func Round(f float64, dec int) float64 { shift := math.Pow10(dec) tmp := f * shift @@ -40,10 +39,8 @@ func Round(f float64, dec int) float64 { return result } -// RoundInt rounds the argument i to dec decimal places. +// RoundInt rounds the argument i to dec decimal places using "round half up" rule. // dec defaults to 0 if not specified. dec can be negative -// to cause dec digits left of the decimal point of the -// value f to become zero. func RoundInt(i int64, dec int) int64 { // is itself when dec >= 0 if dec >= 0 { From 53f0135494bc3c51f71222d6938a304455968fd7 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:31:05 +0800 Subject: [PATCH 07/12] rename Round to RoundFloat --- expression/builtin_math.go | 6 +++--- expression/builtin_math_vec.go | 6 +++--- types/etc_test.go | 2 +- types/helper.go | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/expression/builtin_math.go b/expression/builtin_math.go index 7891c30e69fc2..3224d5c5834b1 100644 --- a/expression/builtin_math.go +++ b/expression/builtin_math.go @@ -353,7 +353,7 @@ func (b *builtinRoundRealSig) evalReal(row chunk.Row) (float64, bool, error) { if isNull || err != nil { return 0, isNull, err } - return types.Round(val, 0), false, nil + return types.RoundFloat(val, 0), false, nil } type builtinRoundIntSig struct { @@ -417,7 +417,7 @@ func (b *builtinRoundWithFracRealSig) evalReal(row chunk.Row) (float64, bool, er if isNull || err != nil { return 0, isNull, err } - return types.Round(val, int(frac)), false, nil + return types.RoundFloat(val, int(frac)), false, nil } type builtinRoundWithFracIntSig struct { @@ -441,7 +441,7 @@ func (b *builtinRoundWithFracIntSig) evalInt(row chunk.Row) (int64, bool, error) if isNull || err != nil { return 0, isNull, err } - return int64(types.Round(float64(val), int(frac))), false, nil + return int64(types.RoundFloat(float64(val), int(frac))), false, nil } type builtinRoundWithFracDecSig struct { diff --git a/expression/builtin_math_vec.go b/expression/builtin_math_vec.go index 0835a8ad2aad8..0130b3847a194 100644 --- a/expression/builtin_math_vec.go +++ b/expression/builtin_math_vec.go @@ -517,7 +517,7 @@ func (b *builtinRoundRealSig) vecEvalReal(input *chunk.Chunk, result *chunk.Colu if result.IsNull(i) { continue } - f64s[i] = types.Round(f64s[i], 0) + f64s[i] = types.RoundFloat(f64s[i], 0) } return nil } @@ -547,7 +547,7 @@ func (b *builtinRoundWithFracRealSig) vecEvalReal(input *chunk.Chunk, result *ch if result.IsNull(i) { continue } - x[i] = types.Round(x[i], int(d[i])) + x[i] = types.RoundFloat(x[i], int(d[i])) } return nil } @@ -654,7 +654,7 @@ func (b *builtinRoundWithFracIntSig) vecEvalInt(input *chunk.Chunk, result *chun if result.IsNull(i) { continue } - i64s[i] = int64(types.Round(float64(i64s[i]), int(frac[i]))) + i64s[i] = int64(types.RoundFloat(float64(i64s[i]), int(frac[i]))) } return nil } diff --git a/types/etc_test.go b/types/etc_test.go index 66b9a40c55e63..1d2ff727e1094 100644 --- a/types/etc_test.go +++ b/types/etc_test.go @@ -148,7 +148,7 @@ func (s *testTypeEtcSuite) TestRound(c *C) { } for _, t := range tbl { - f := Round(t.Input, t.Dec) + f := RoundFloat(t.Input, t.Dec) c.Assert(f, Equals, t.Expect) } } diff --git a/types/helper.go b/types/helper.go index b835b06e49e01..f1f60a01050c2 100644 --- a/types/helper.go +++ b/types/helper.go @@ -23,10 +23,10 @@ import ( "github.com/pingcap/errors" ) -// Round rounds the argument f to dec decimal places using "round to nearest even" rule. +// RoundFloat rounds the argument f to dec decimal places using "round to nearest even" rule. // dec defaults to 0 if not specified. dec can be negative // see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html -func Round(f float64, dec int) float64 { +func RoundFloat(f float64, dec int) float64 { shift := math.Pow10(dec) tmp := f * shift if math.IsInf(tmp, 0) { @@ -90,7 +90,7 @@ func TruncateFloat(f float64, flen int, decimal int) (float64, error) { maxF := GetMaxFloat(flen, decimal) if !math.IsInf(f, 0) { - f = Round(f, decimal) + f = RoundFloat(f, decimal) } var err error From b29434144a76c186d34826520ebfc4664738ad44 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Tue, 17 Aug 2021 23:34:35 +0800 Subject: [PATCH 08/12] int use RoundInt --- expression/builtin_math.go | 2 +- expression/builtin_math_vec.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/expression/builtin_math.go b/expression/builtin_math.go index 3224d5c5834b1..7bc918330d9a8 100644 --- a/expression/builtin_math.go +++ b/expression/builtin_math.go @@ -441,7 +441,7 @@ func (b *builtinRoundWithFracIntSig) evalInt(row chunk.Row) (int64, bool, error) if isNull || err != nil { return 0, isNull, err } - return int64(types.RoundFloat(float64(val), int(frac))), false, nil + return types.RoundInt(val, int(frac)), false, nil } type builtinRoundWithFracDecSig struct { diff --git a/expression/builtin_math_vec.go b/expression/builtin_math_vec.go index 0130b3847a194..22af698dc0db5 100644 --- a/expression/builtin_math_vec.go +++ b/expression/builtin_math_vec.go @@ -654,7 +654,7 @@ func (b *builtinRoundWithFracIntSig) vecEvalInt(input *chunk.Chunk, result *chun if result.IsNull(i) { continue } - i64s[i] = int64(types.RoundFloat(float64(i64s[i]), int(frac[i]))) + i64s[i] = types.RoundInt(i64s[i], int(frac[i])) } return nil } From 4c0e120d4eb2c466634bbe7e2252ff3db7c2732d Mon Sep 17 00:00:00 2001 From: feitian124 Date: Thu, 19 Aug 2021 23:42:53 +0800 Subject: [PATCH 09/12] fix ci issue: fmt error --- types/helper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/types/helper.go b/types/helper.go index f1f60a01050c2..367e64d296567 100644 --- a/types/helper.go +++ b/types/helper.go @@ -57,7 +57,6 @@ func RoundInt(i int64, dec int) int64 { return r } - // Truncate truncates the argument f to dec decimal places. // dec defaults to 0 if not specified. dec can be negative // to cause dec digits left of the decimal point of the From dd67250e057adecaa06f6587b561677d977f09e1 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Fri, 20 Aug 2021 22:18:49 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E5=88=A0=E6=8E=89=E5=8F=AF=E8=83=BD?= =?UTF-8?q?=E5=AF=BC=E8=87=B4block=E7=9A=84=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- expression/integration_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/expression/integration_test.go b/expression/integration_test.go index 130fd161aa374..8b1d0f44b798a 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -700,11 +700,6 @@ func (s *testIntegrationSuite2) TestMathBuiltin(c *C) { result.Check(testkit.Rows("0 100 100")) result = tk.MustQuery("SELECT round(123456789, -5), round(2146213728964879326, -15) ") result.Check(testkit.Rows("123500000 2146000000000000000")) - // MySQL 8 will report an error if round result overflows - rs, err = tk.Exec("select round(18446744073709551615, -19);") - c.Assert(err, NotNil) - terr = errors.Cause(err).(*terror.Error) - c.Assert(terr.Code(), Equals, errors.ErrCode(mysql.ErrDataOutOfRange)) // for truncate result = tk.MustQuery("SELECT truncate(123, -2), truncate(123, 2), truncate(123, 1), truncate(123, -1);") From 0f088e62175a9c6616ee86c469a225aeb713b4b1 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Sun, 22 Aug 2021 10:14:33 +0800 Subject: [PATCH 11/12] use power instead of for loop --- types/helper.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/types/helper.go b/types/helper.go index 367e64d296567..5d3a1407674b8 100644 --- a/types/helper.go +++ b/types/helper.go @@ -46,15 +46,10 @@ func RoundInt(i int64, dec int) int64 { if dec >= 0 { return i } - // The integer part of a fraction - shift := math.Pow10(dec) - intPart := math.Round(float64(i) * shift) - r := int64(intPart) - // the zero part - for i := 1; i <= -dec; i++ { - r *= 10 - } - return r + + shift := math.Pow10(-dec) + intPart := math.Round(float64(i) / shift) + return int64(intPart) * int64(shift) } // Truncate truncates the argument f to dec decimal places. From 9593daabded86030242479aca3f47fae4ebb7c57 Mon Sep 17 00:00:00 2001 From: feitian124 Date: Wed, 25 Aug 2021 00:27:51 +0800 Subject: [PATCH 12/12] add test case for avoid float calc in int round --- expression/integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/expression/integration_test.go b/expression/integration_test.go index 8b1d0f44b798a..e5f71adb119f8 100644 --- a/expression/integration_test.go +++ b/expression/integration_test.go @@ -698,8 +698,8 @@ func (s *testIntegrationSuite2) TestMathBuiltin(c *C) { result.Check(testkit.Rows("0 0 0")) result = tk.MustQuery("SELECT round(49.99999, -2), round(50, -2), round(50.00001, -2)") result.Check(testkit.Rows("0 100 100")) - result = tk.MustQuery("SELECT round(123456789, -5), round(2146213728964879326, -15) ") - result.Check(testkit.Rows("123500000 2146000000000000000")) + result = tk.MustQuery("SELECT round(123456789, -5), round(2146213728964879326, -15), round(24999999999999999,-16)") + result.Check(testkit.Rows("123500000 2146000000000000000 20000000000000000")) // for truncate result = tk.MustQuery("SELECT truncate(123, -2), truncate(123, 2), truncate(123, 1), truncate(123, -1);")