~aleteoryx/muditaos

c2cd94a6f2aa3f54ba0559928a5f21e4fc042584 — KacperLewandowski 5 years ago cb3b6cd
[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.
M module-apps/application-calculator/data/CalculatorUtility.cpp => module-apps/application-calculator/data/CalculatorUtility.cpp +70 -0
@@ 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<long long>(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<unsigned long int>(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);
}

M module-apps/application-calculator/data/CalculatorUtility.hpp => module-apps/application-calculator/data/CalculatorUtility.hpp +15 -0
@@ 4,6 4,17 @@
#pragma once
#include <string>

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);

M module-apps/application-calculator/tests/CalculatorUtility_tests.cpp => module-apps/application-calculator/tests/CalculatorUtility_tests.cpp +47 -0
@@ 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);
    }
}

M module-apps/application-calculator/widgets/CalculatorStyle.hpp => module-apps/application-calculator/widgets/CalculatorStyle.hpp +1 -0
@@ 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

M module-apps/application-calculator/windows/CalculatorMainWindow.cpp => module-apps/application-calculator/windows/CalculatorMainWindow.cpp +42 -7
@@ 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<int> 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;
        }

M module-apps/application-calculator/windows/CalculatorMainWindow.hpp => module-apps/application-calculator/windows/CalculatorMainWindow.hpp +2 -0
@@ 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);

M module-utils/Utils.hpp => module-utils/Utils.hpp +7 -0
@@ 122,7 122,14 @@ namespace utils
                baseAsStr = "-0";
            }
        }

        auto fractionalPart = static_cast<unsigned long int>(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";