From c2cd94a6f2aa3f54ba0559928a5f21e4fc042584 Mon Sep 17 00:00:00 2001 From: KacperLewandowski Date: Mon, 11 Jan 2021 00:12:46 +0100 Subject: [PATCH] [EGD-4417] Fix operations in calculator 1. Fix possibility to write illogical operations. 2. Fix possibility to write multiple '0' when it is not needed. 3. Add scientific notation when result do not fit on screen. --- .../data/CalculatorUtility.cpp | 70 +++++++++++++++++++ .../data/CalculatorUtility.hpp | 15 ++++ .../tests/CalculatorUtility_tests.cpp | 47 +++++++++++++ .../widgets/CalculatorStyle.hpp | 1 + .../windows/CalculatorMainWindow.cpp | 49 +++++++++++-- .../windows/CalculatorMainWindow.hpp | 2 + module-utils/Utils.hpp | 7 ++ 7 files changed, 184 insertions(+), 7 deletions(-) diff --git a/module-apps/application-calculator/data/CalculatorUtility.cpp b/module-apps/application-calculator/data/CalculatorUtility.cpp index 332cadc354de1254e7e5941f79a14d0ef9a0439b..584788b58d714e3fb3172b30e709e9fab4336ea1 100644 --- a/module-apps/application-calculator/data/CalculatorUtility.cpp +++ b/module-apps/application-calculator/data/CalculatorUtility.cpp @@ -15,6 +15,9 @@ Result Calculator::calculate(std::string source) double result = te_interp(source.c_str(), &error); if (error == 0 && !std::isinf(result) && !std::isnan(result)) { auto output = utils::to_string(result); + if (output.length() > CalculatorConstants::maxStringLength) { + output = getValueThatFitsOnScreen(result); + } if (utils::localize.get("app_calculator_decimal_separator") == style::calculator::symbols::strings::comma) { output.replace(output.find(style::calculator::symbols::strings::full_stop), std::size(std::string_view(style::calculator::symbols::strings::full_stop)), @@ -48,3 +51,70 @@ std::string Calculator::replaceAllOccurrences(std::string input, const std::stri } return input; } + +std::string Calculator::getValueThatFitsOnScreen(double result) +{ + auto base = static_cast(result); + auto length = utils::to_string(base).length(); + if (base < 0) { + length -= 1; + } + if (length > CalculatorConstants::expLength + 1) { + return convertToNumberWithPositiveExponent(result, length - 1); + } + else if (length == CalculatorConstants::expLength + 1) { + if (result < 0) { + return utils::to_string(getCoefficient(result, CalculatorConstants::veryLowPrecision)); + } + return utils::to_string(getCoefficient(result, CalculatorConstants::lowPrecision)); + } + else if (length == 1 && result < -1) { + return utils::to_string(getCoefficient(result, CalculatorConstants::lowPrecision)); + } + else if (result > 1) { + return utils::to_string(getCoefficient(result, CalculatorConstants::precision)); + } + else { + return convertToNumberWithNegativeExponent(result, base); + } +} + +std::string Calculator::convertToNumberWithPositiveExponent(double result, uint32_t exponent) +{ + result /= pow(10, exponent); + auto exponentLength = utils::to_string(exponent).length(); + auto decimalPlace = CalculatorConstants::precision - exponentLength - CalculatorConstants::expLength; + if (result < 0) { + decimalPlace -= 1; + } + return utils::to_string(getCoefficient(result, decimalPlace)) + "e" + utils::to_string(exponent); +} + +std::string Calculator::convertToNumberWithNegativeExponent(double result, long long base) +{ + double frac = (result - base) * pow(10, CalculatorConstants::highPrecision); + if (result < 0) { + frac *= -1; + } + auto fractionalPart = static_cast(round(frac)); + auto fracLength = utils::to_string(fractionalPart).length(); + auto exponent = CalculatorConstants::highPrecision - fracLength + 1; + if (exponent > CalculatorConstants::minusExpLength + 1) { + result *= pow(10, exponent); + auto exponentLength = utils::to_string(exponent).length(); + auto decimalPlace = CalculatorConstants::precision - exponentLength - CalculatorConstants::minusExpLength; + if (result < 0) { + decimalPlace -= 1; + } + return utils::to_string(getCoefficient(result, decimalPlace)) + "e-" + utils::to_string(exponent); + } + else if (result < 0) { + return utils::to_string(getCoefficient(result, CalculatorConstants::lowPrecision)); + } + return utils::to_string(getCoefficient(result, CalculatorConstants::precision)); +} + +long double Calculator::getCoefficient(double result, uint32_t precision) +{ + return std::roundl(result * pow(10, precision)) / pow(10, precision); +} diff --git a/module-apps/application-calculator/data/CalculatorUtility.hpp b/module-apps/application-calculator/data/CalculatorUtility.hpp index a3f2796e9e206260abf3d7c3ed9f71c7632dc06b..90f8c9b82c16908ca98759fdcca978cc55d5c973 100644 --- a/module-apps/application-calculator/data/CalculatorUtility.hpp +++ b/module-apps/application-calculator/data/CalculatorUtility.hpp @@ -4,6 +4,17 @@ #pragma once #include +namespace CalculatorConstants +{ + inline constexpr auto veryLowPrecision = 4; + inline constexpr auto lowPrecision = 5; + inline constexpr auto precision = 6; + inline constexpr auto highPrecision = 8; + inline constexpr auto expLength = 1; + inline constexpr auto minusExpLength = 2; + inline constexpr auto maxStringLength = 7; +} // namespace CalculatorConstants + struct Result { std::string equation; @@ -18,6 +29,10 @@ class Calculator /// @return: new string with replaced all occurrences of given string to the new one in whole input std::string replaceAllOccurrences(std::string input, const std::string &from, const std::string &to); std::string prepareEquationForParser(std::string input); + std::string getValueThatFitsOnScreen(double result); + long double getCoefficient(double result, uint32_t precision); + std::string convertToNumberWithPositiveExponent(double result, uint32_t exponent); + std::string convertToNumberWithNegativeExponent(double result, long long base); public: Result calculate(std::string source); diff --git a/module-apps/application-calculator/tests/CalculatorUtility_tests.cpp b/module-apps/application-calculator/tests/CalculatorUtility_tests.cpp index 0ff3e1f1c6a2298f8d08f7793b8e7e1c5336f230..6f10b48d67ef48a4f3b19318af9d6065ef79aa4d 100644 --- a/module-apps/application-calculator/tests/CalculatorUtility_tests.cpp +++ b/module-apps/application-calculator/tests/CalculatorUtility_tests.cpp @@ -103,4 +103,51 @@ TEST_CASE("Calculator utilities") REQUIRE(result.equation == "1.79769e+308*2"); REQUIRE(result.isError); } + + SECTION("Round to fit in screen") + { + auto result = calculator.calculate("1.1234512345"); + REQUIRE(result.value == "1.123451"); + REQUIRE(!result.isError); + + result = calculator.calculate("0.0567891"); + REQUIRE(result.value == "0.056789"); + REQUIRE(!result.isError); + + result = calculator.calculate("-0.056789"); + REQUIRE(result.value == "-0.05679"); + REQUIRE(!result.isError); + + result = calculator.calculate("15.556789"); + REQUIRE(result.value == "15.55679"); + REQUIRE(!result.isError); + } + + SECTION("Change to scientific notation (number > 0)") + { + auto result = calculator.calculate("12345.55555"); + REQUIRE(result.value == "1.2346e4"); + REQUIRE(!result.isError); + } + + SECTION("Change to scientific notation (number < 0)") + { + auto result = calculator.calculate("-12345.55555"); + REQUIRE(result.value == "-1.235e4"); + REQUIRE(!result.isError); + } + + SECTION("Change to scientific notation (0 < number < 1)") + { + auto result = calculator.calculate("0.000456712"); + REQUIRE(result.value == "4.567e-4"); + REQUIRE(!result.isError); + } + + SECTION("Change to scientific notation (-1 < number < 0)") + { + auto result = calculator.calculate("-0.000456712"); + REQUIRE(result.value == "-4.57e-4"); + REQUIRE(!result.isError); + } } diff --git a/module-apps/application-calculator/widgets/CalculatorStyle.hpp b/module-apps/application-calculator/widgets/CalculatorStyle.hpp index 7b7fc7af02d27346693243b74486b47a000914ad..7659e4679a3982a54f0a93a1b18c43d68b95a764 100644 --- a/module-apps/application-calculator/widgets/CalculatorStyle.hpp +++ b/module-apps/application-calculator/widgets/CalculatorStyle.hpp @@ -34,6 +34,7 @@ namespace style::calculator inline constexpr auto full_stop = 0x002E; inline constexpr auto comma = 0x002C; inline constexpr auto equals = 0x003D; + inline constexpr auto zero = 0x0030; } // namespace codes namespace strings diff --git a/module-apps/application-calculator/windows/CalculatorMainWindow.cpp b/module-apps/application-calculator/windows/CalculatorMainWindow.cpp index 7aa76e2508460875f3c661855e3e9056c0794792..da1af14fb28d609a610ea5745aeb9feb281d07c0 100644 --- a/module-apps/application-calculator/windows/CalculatorMainWindow.cpp +++ b/module-apps/application-calculator/windows/CalculatorMainWindow.cpp @@ -63,8 +63,15 @@ namespace gui if (!event.isShortPress()) { return false; } + if (event.is(gui::KeyCode::KEY_0) && mathOperationInput->getText() == "0") { + return true; + } auto lastChar = mathOperationInput->getText()[mathOperationInput->getText().length() - 1]; bool lastCharIsSymbol = isSymbol(lastChar); + if (lastChar == style::calculator::symbols::codes::zero && isSymbol(getPenultimate()) && + !isDecimalSeparator(getPenultimate()) && event.is(gui::KeyCode::KEY_0)) { + return true; + } if (event.keyCode == gui::KeyCode::KEY_UP) { writeEquation(lastCharIsSymbol, style::calculator::symbols::strings::plus); return true; @@ -89,6 +96,15 @@ namespace gui } return true; } + if (lastChar == style::calculator::symbols::codes::zero && isSymbol(getPenultimate()) && + !isDecimalSeparator(getPenultimate()) && !event.is(gui::KeyCode::KEY_0) && + !event.is(gui::KeyCode::KEY_PND) && !event.is(gui::KeyCode::KEY_ENTER)) { + mathOperationInput->removeChar(); + return false; + } + if (!event.is(gui::KeyCode::KEY_0) && mathOperationInput->getText() == "0") { + mathOperationInput->clear(); + } return false; }; } @@ -103,21 +119,36 @@ namespace gui character == style::calculator::symbols::codes::full_stop; } + bool CalculatorMainWindow::isDecimalSeparator(uint32_t character) + { + return character == style::calculator::symbols::codes::comma || + character == style::calculator::symbols::codes::full_stop; + } + + uint32_t CalculatorMainWindow::getPenultimate() + { + if (mathOperationInput->getText().length() > 1) { + return mathOperationInput->getText()[mathOperationInput->getText().length() - 2]; + } + return 0; + } + void CalculatorMainWindow::writeEquation(bool lastCharIsSymbol, const UTF8 &symbol) { if (!mathOperationInput->getText().empty()) { if (lastCharIsSymbol && symbol != style::calculator::symbols::strings::minus) { - mathOperationInput->setRichText( - std::string(mathOperationInput->getText()).erase(mathOperationInput->getText().length() - 1) + - symbol.c_str()); + if (!isSymbol(getPenultimate()) && mathOperationInput->getText().length() > 1) { + mathOperationInput->removeChar(); + mathOperationInput->addText(symbol); + } } else { - mathOperationInput->setRichText(mathOperationInput->getText() + symbol); + mathOperationInput->addText(symbol); } } else if (symbol == style::calculator::symbols::strings::minus) { - mathOperationInput->setRichText(mathOperationInput->getText() + symbol); + mathOperationInput->addText(symbol); } } @@ -126,7 +157,11 @@ namespace gui if (!mathOperationInput->getText().empty()) { std::vector symbolsIndexes; auto input = std::string(mathOperationInput->getText()).erase(mathOperationInput->getText().length() - 1); - symbolsIndexes.push_back(input.find_last_of(style::calculator::symbols::strings::minus)); + auto exponentIndex = input.find_last_of('e'); + auto minusIndex = input.find_last_of(style::calculator::symbols::strings::minus); + if (minusIndex != exponentIndex + 1) { + symbolsIndexes.push_back(minusIndex); + } symbolsIndexes.push_back(input.find_last_of(style::calculator::symbols::strings::plus)); symbolsIndexes.push_back(input.find_last_of(style::calculator::symbols::strings::division)); symbolsIndexes.push_back(input.find_last_of(style::calculator::symbols::strings::multiplication)); @@ -156,7 +191,7 @@ namespace gui if (inputEvent.keyCode == gui::KeyCode::KEY_ENTER) { auto result = Calculator().calculate(std::string(mathOperationInput->getText())); - mathOperationInput->setRichText(result.value); + mathOperationInput->setText(result.value); clearInput = result.isError; return true; } diff --git a/module-apps/application-calculator/windows/CalculatorMainWindow.hpp b/module-apps/application-calculator/windows/CalculatorMainWindow.hpp index a1720dd474b5bc47814aadff722402fdd7acb931..7208fd54533daeaba0e8f90a87004bd2285e0bfd 100644 --- a/module-apps/application-calculator/windows/CalculatorMainWindow.hpp +++ b/module-apps/application-calculator/windows/CalculatorMainWindow.hpp @@ -22,6 +22,8 @@ namespace gui void applyInputCallback(); bool isPreviousNumberDecimal(); bool isSymbol(uint32_t character); + bool isDecimalSeparator(uint32_t character); + uint32_t getPenultimate(); public: CalculatorMainWindow(app::Application *app, std::string name); diff --git a/module-utils/Utils.hpp b/module-utils/Utils.hpp index 55fe81daf45e31678e53a035c0ea9455e3339b7c..26da74259daf9a2ebbe788a5320e13fa48914dc7 100644 --- a/module-utils/Utils.hpp +++ b/module-utils/Utils.hpp @@ -122,7 +122,14 @@ namespace utils baseAsStr = "-0"; } } + auto fractionalPart = static_cast(roundl(frac)); + auto fractionalPartLength = std::to_string(fractionalPart).length(); + if (fractionalPartLength > precision) { + base += 1; + baseAsStr = std::to_string(base); + fractionalPart = 0; + } if (fractionalPart == 0) { if (baseAsStr == "-0") { return "0";