diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index 07463ced482..0d9e6cd93ee 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -5,8 +5,10 @@ #include "FunctionManager.h" +#include #include +#include #include #include @@ -68,8 +70,11 @@ std::unordered_map> FunctionManager::typ {"round", {TypeSignature({Value::Type::INT}, Value::Type::FLOAT), TypeSignature({Value::Type::INT, Value::Type::INT}, Value::Type::FLOAT), + TypeSignature({Value::Type::INT, Value::Type::INT, Value::Type::STRING}, Value::Type::FLOAT), TypeSignature({Value::Type::FLOAT}, Value::Type::FLOAT), - TypeSignature({Value::Type::FLOAT, Value::Type::INT}, Value::Type::FLOAT)}}, + TypeSignature({Value::Type::FLOAT, Value::Type::INT}, Value::Type::FLOAT), + TypeSignature({Value::Type::FLOAT, Value::Type::INT, Value::Type::STRING}, + Value::Type::FLOAT)}}, {"sqrt", {TypeSignature({Value::Type::INT}, Value::Type::FLOAT), TypeSignature({Value::Type::FLOAT}, Value::Type::FLOAT)}}, @@ -568,7 +573,7 @@ FunctionManager::FunctionManager() { // to nearest integral (as a floating-point value) auto &attr = functions_["round"]; attr.minArity_ = 1; - attr.maxArity_ = 2; + attr.maxArity_ = 3; attr.isAlwaysPure_ = true; attr.body_ = [](const auto &args) -> Value { switch (args[0].get().type()) { @@ -577,10 +582,60 @@ FunctionManager::FunctionManager() { } case Value::Type::INT: case Value::Type::FLOAT: { - if (args.size() == 2) { + if (args.size() >= 2) { if (args[1].get().type() == Value::Type::INT) { + auto val = args[0].get().getFloat(); auto decimal = args[1].get().getInt(); - return std::round(args[0].get().getFloat() * pow(10, decimal)) / pow(10, decimal); + auto factor = pow(10.0, decimal); + + string mode = "half_up"; + if (args.size() == 3 && args[2].get().type() == Value::Type::STRING) { + mode = args[2].get().getStr(); + } + if (boost::iequals(mode, "up")) { + auto roundedVal = std::round(val * factor) / factor; + if ((val < 0) && (roundedVal > val)) { + roundedVal -= 1.0 / factor; + } else if ((val > 0) && (roundedVal < val)) { + roundedVal += 1.0 / factor; + } + return roundedVal; + } else if (boost::iequals(mode, "down")) { + auto roundedVal = std::round(val * factor) / factor; + if ((val < 0) && (roundedVal < val)) { + roundedVal += 1.0 / factor; + } else if ((val > 0) && (roundedVal > val)) { + roundedVal -= 1.0 / factor; + } + return roundedVal; + } else if (boost::iequals(mode, "ceiling")) { + return std::ceil(val * factor) / factor; + } else if (boost::iequals(mode, "floor")) { + return std::floor(val * factor) / factor; + } else if (boost::iequals(mode, "half_up")) { + return std::round(val * factor) / factor; + } else if (boost::iequals(mode, "half_down")) { + auto val_factor = val * factor; + auto integral_part = 0.0; + auto fraction_part = std::fabs(std::modf(val_factor, &integral_part)); + if (((fraction_part <= 0.5) && (val < 0)) || ((fraction_part > 0.5) && (val > 0))) { + return std::ceil(val_factor) / factor; + } else { + return std::floor(val_factor) / factor; + } + } else if (boost::iequals(mode, "half_even")) { + auto val_factor = val * factor; + auto integral_part = 0.0; + auto fraction_part = std::fabs(std::modf(val_factor, &integral_part)); + if (((fraction_part == 0.5) && (std::fmod(val_factor - 0.5, 2.0) == 1.0)) || + ((fraction_part > 0.5) && (val > 0)) || ((fraction_part < 0.5) && (val < 0))) { + return std::ceil(val_factor) / factor; + } else { + return std::floor(val_factor) / factor; + } + } else { + return Value::kNullBadType; + } } else { return Value::kNullBadType; } diff --git a/src/common/function/test/FunctionManagerTest.cpp b/src/common/function/test/FunctionManagerTest.cpp index be68924d4e6..88ff49888df 100644 --- a/src/common/function/test/FunctionManagerTest.cpp +++ b/src/common/function/test/FunctionManagerTest.cpp @@ -107,6 +107,40 @@ std::unordered_map> FunctionManagerTest::args_ = {"round1", {11111.11111, 2}}, {"round2", {11111.11111, -1}}, {"round3", {11111.11111, -5}}, + {"round4", {1.249, 1, "up"}}, + {"round5", {-1.251, 1, "up"}}, + {"round6", {1.25, 1, "up"}}, + {"round7", {-1.35, 1, "up"}}, + {"round8", {1.249, 1, "down"}}, + {"round9", {-1.251, 1, "down"}}, + {"round10", {1.25, 1, "down"}}, + {"round11", {-1.35, 1, "down"}}, + {"round12", {1.249, 1, "ceiling"}}, + {"round13", {-1.251, 1, "ceiling"}}, + {"round14", {1.25, 1, "ceiling"}}, + {"round15", {-1.35, 1, "ceiling"}}, + {"round16", {1.249, 1, "floor"}}, + {"round17", {-1.251, 1, "floor"}}, + {"round18", {1.25, 1, "floor"}}, + {"round19", {-1.35, 1, "floor"}}, + {"round20", {1.249, 1, "half_up"}}, + {"round21", {-1.251, 1, "half_up"}}, + {"round22", {1.25, 1, "half_up"}}, + {"round23", {-1.35, 1, "half_up"}}, + {"round24", {1.249, 1, "half_down"}}, + {"round25", {-1.251, 1, "half_down"}}, + {"round26", {1.25, 1, "half_down"}}, + {"round27", {-1.35, 1, "half_down"}}, + {"round28", {1.249, 1, "half_even"}}, + {"round29", {-1.251, 1, "half_even"}}, + {"round30", {1.25, 1, "half_even"}}, + {"round31", {-1.35, 1, "half_even"}}, + {"round32", {1.5, 0, "half_up"}}, + {"round33", {1.5, 0, "half_down"}}, + {"round34", {12.22, 0, "half_up"}}, + {"round35", {1.5, 0}}, + {"round36", {-1.5, 1}}, + {"round37", {1.25, 1, "dummy_mode"}}, {"radians", {180}}, {"range1", {1, 5}}, {"range2", {1, 5, 2}}, @@ -282,6 +316,40 @@ TEST_F(FunctionManagerTest, functionCall) { TEST_FUNCTION(round, args_["round1"], 11111.11); TEST_FUNCTION(round, args_["round2"], 11110.0); TEST_FUNCTION(round, args_["round3"], 0.0); + TEST_FUNCTION(round, args_["round4"], 1.3); + TEST_FUNCTION(round, args_["round5"], -1.3); + TEST_FUNCTION(round, args_["round6"], 1.3); + TEST_FUNCTION(round, args_["round7"], -1.4); + TEST_FUNCTION(round, args_["round8"], 1.2); + TEST_FUNCTION(round, args_["round9"], -1.2); + TEST_FUNCTION(round, args_["round10"], 1.2); + TEST_FUNCTION(round, args_["round11"], -1.3); + TEST_FUNCTION(round, args_["round12"], 1.3); + TEST_FUNCTION(round, args_["round13"], -1.2); + TEST_FUNCTION(round, args_["round14"], 1.3); + TEST_FUNCTION(round, args_["round15"], -1.3); + TEST_FUNCTION(round, args_["round16"], 1.2); + TEST_FUNCTION(round, args_["round17"], -1.3); + TEST_FUNCTION(round, args_["round18"], 1.2); + TEST_FUNCTION(round, args_["round19"], -1.4); + TEST_FUNCTION(round, args_["round20"], 1.2); + TEST_FUNCTION(round, args_["round21"], -1.3); + TEST_FUNCTION(round, args_["round22"], 1.3); + TEST_FUNCTION(round, args_["round23"], -1.4); + TEST_FUNCTION(round, args_["round24"], 1.2); + TEST_FUNCTION(round, args_["round25"], -1.3); + TEST_FUNCTION(round, args_["round26"], 1.2); + TEST_FUNCTION(round, args_["round27"], -1.3); + TEST_FUNCTION(round, args_["round28"], 1.2); + TEST_FUNCTION(round, args_["round29"], -1.3); + TEST_FUNCTION(round, args_["round30"], 1.2); + TEST_FUNCTION(round, args_["round31"], -1.4); + TEST_FUNCTION(round, args_["round32"], 2.0); + TEST_FUNCTION(round, args_["round33"], 1.0); + TEST_FUNCTION(round, args_["round34"], 12.0); + TEST_FUNCTION(round, args_["round35"], 2.0); + TEST_FUNCTION(round, args_["round36"], -1.5); + TEST_FUNCTION(round, args_["round37"], Value::kNullBadData); } { TEST_FUNCTION(range, args_["range1"], Value(List({1, 2, 3, 4, 5}))); diff --git a/tests/tck/features/function/round.feature b/tests/tck/features/function/round.feature new file mode 100644 index 00000000000..f56d0e7d3bc --- /dev/null +++ b/tests/tck/features/function/round.feature @@ -0,0 +1,236 @@ +Feature: Round + + Background: + Test round function + + Scenario: test `up` mode + When executing query: + """ + RETURN round(1.249, 1, "up") as result + """ + Then the result should be, in any order: + | result | + | 1.3 | + When executing query: + """ + RETURN round(-1.251, 1, "up") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + When executing query: + """ + RETURN round(1.25, 1, "up") as result + """ + Then the result should be, in any order: + | result | + | 1.3 | + When executing query: + """ + RETURN round(-1.35, 1, "up") as result + """ + Then the result should be, in any order: + | result | + | -1.4 | + + Scenario: test `down` mode + When executing query: + """ + RETURN round(1.249, 1, "down") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.251, 1, "down") as result + """ + Then the result should be, in any order: + | result | + | -1.2 | + When executing query: + """ + RETURN round(1.25, 1, "down") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.35, 1, "down") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + + Scenario: test `ceiling` mode + When executing query: + """ + RETURN round(1.249, 1, "ceiling") as result + """ + Then the result should be, in any order: + | result | + | 1.3 | + When executing query: + """ + RETURN round(-1.251, 1, "ceiling") as result + """ + Then the result should be, in any order: + | result | + | -1.2 | + When executing query: + """ + RETURN round(1.25, 1, "ceiling") as result + """ + Then the result should be, in any order: + | result | + | 1.3 | + When executing query: + """ + RETURN round(-1.35, 1, "ceiling") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + + Scenario: test `floor` mode + When executing query: + """ + RETURN round(1.249, 1, "floor") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.251, 1, "floor") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + When executing query: + """ + RETURN round(1.25, 1, "floor") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.35, 1, "floor") as result + """ + Then the result should be, in any order: + | result | + | -1.4 | + + Scenario: test `half_up` mode + When executing query: + """ + RETURN round(1.249, 1, "half_up") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.251, 1, "half_up") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + When executing query: + """ + RETURN round(1.25, 1, "half_up") as result + """ + Then the result should be, in any order: + | result | + | 1.3 | + When executing query: + """ + RETURN round(-1.35, 1, "half_up") as result + """ + Then the result should be, in any order: + | result | + | -1.4 | + + Scenario: test `half_down` mode + When executing query: + """ + RETURN round(1.249, 1, "half_down") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.251, 1, "half_down") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + When executing query: + """ + RETURN round(1.25, 1, "half_down") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.35, 1, "half_down") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + + Scenario: test `half_even` mode + When executing query: + """ + RETURN round(1.249, 1, "half_even") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.251, 1, "half_even") as result + """ + Then the result should be, in any order: + | result | + | -1.3 | + When executing query: + """ + RETURN round(1.25, 1, "half_even") as result + """ + Then the result should be, in any order: + | result | + | 1.2 | + When executing query: + """ + RETURN round(-1.35, 1, "half_even") as result + """ + Then the result should be, in any order: + | result | + | -1.4 | + + Scenario: test bad_type + When executing query: + """ + RETURN round(3.125, 3.2) as result + """ + Then a SemanticError should be raised at runtime: `round(3.125,3.2)' is not a valid expression : Parameter's type error + When executing query: + """ + RETURN round(3.125, 3.2, 42) as result + """ + Then a SemanticError should be raised at runtime: `round(3.125,3.2,42)' is not a valid expression : Parameter's type error + When executing query: + """ + RETURN round("3.124", 3) as result + """ + Then a SemanticError should be raised at runtime: `round("3.124",3)' is not a valid expression : Parameter's type error + When executing query: + """ + RETURN round(1.4, "fs", "half_up") as result + """ + Then a SemanticError should be raised at runtime: `round(1.4,"fs","half_up")' is not a valid expression : Parameter's type error