Evaluate a math expression in Qt
Asked Answered
M

3

18

I'm trying to create a Qt application and I need a math expression evaluator to evaluate things like this e.g. (4+5)*2-9/3. I included the .hpp file of this library (http://www.partow.net/programming/exprtk/) to my project in the Qt Creator and tried to launch the following example of code:

#include <cstdio>
#include <string>
#include "exprtk.hpp"

int main()
{
   typedef exprtk::expression<double> expression_t;
   typedef exprtk::parser<double>         parser_t;

   std::string expression_string = "3 + sqrt(5) + pow(3,2) + log(5)";

   expression_t expression;

   parser_t parser;

   if (parser.compile(expression_string,expression))
   {
     double result = expression.value();

     printf("Result: %19.15\n",result);
   }
   else
     printf("Error in expression\n.");

   return 0;
}

When I try to compile and run it I get the following output:

 debug\main.o:-1: error: too many sections (62303)

What could be the problem?

Matronna answered 15/2, 2016 at 18:28 Comment(3)
the line printf("Result: %19.15\n",result); should be printf("Result: %19.15f\n",result);Sigismund
what compiler/os are you using?Sigismund
maybe this helps: https://mcmap.net/q/742260/-gcc-equivalent-of-ms-39-s-bigobjSigismund
S
8

Using just pure Qt you can do something like this:

QString expression_string("3 + Math.sqrt(5) + Math.pow(3,2) + Math.log(5)");
QScriptEngine expression;
double my_val=expression.evaluate(expression_string).toNumber();

you can do much more, see HERE and HERE

Sigismund answered 15/2, 2016 at 18:39 Comment(0)
H
0

Actually, on my machine (Qt 5.5, Ubuntu 16.04 with g++ 5.3), the code above does not work.

Despite the answer is quite old, I put my solution in case someone finds it useful.

QScriptEngine uses the JavaScript syntax. So to make the above code work, I had to change the syntax to:

QString expression_string("3 + Math.sqrt(5) + Math.pow(3,2) + Math.log(5)");
QScriptEngine expression;
double my_val=expression.evaluate(expression_string).toNumber();
Hexameter answered 31/1, 2017 at 21:49 Comment(0)
B
0

Following the request in comments, here is how to implement an arithmetic parser using boost::spirit. First, you need to download the boost tarball, don't try to just clone Spirit alone from GitHub, because it has dependencies from other boost libraries.

Boost is huge, so if you want just a subset enough for a parser, you can extract it using bcp. From boost source directory:

cd tools/build/src/engine
./build.sh
cd ../../../bcp
../build/src/engine/b2
cd ../..
dist/bin/bcp fusion/include hana/functional spirit/home/x3 /some/path

bcp will copy all dependencies. You can leave only /some/path/boost directory, because all libraries we need are header only.

Finally, here is the full code of the parser.

#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
#include <vector>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/hana/functional/fix.hpp>
#include <boost/hana/functional/overload.hpp>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>

using namespace boost::spirit;
namespace hana = boost::hana;

// Define AST. The root is `ast::expr`, which is the first left-hand side
// operand and a list of all operations on the right-hand side. Each operand is
// a recursive `variant` that has `ast::expr` inside.
namespace ast
{
    struct nil {};
    struct signed_;
    struct expr;

    struct operand : x3::variant<
        nil
      , double
      , x3::forward_ast<signed_>
      , x3::forward_ast<expr>
      >
    {
        using base_type::base_type;
        using base_type::operator=;
    };

    struct signed_
    {
        char    sign;
        operand operand_;
    };

    struct operation
    {
        char    operator_;
        operand operand_;
    };

    struct expr
    {
        operand                first;
        std::vector<operation> rest;
    };
} // namespace ast

// Give the grammar access to the fields of AST.
BOOST_FUSION_ADAPT_STRUCT(ast::signed_, sign, operand_)
BOOST_FUSION_ADAPT_STRUCT(ast::operation, operator_, operand_)
BOOST_FUSION_ADAPT_STRUCT(ast::expr, first, rest)

// Arithmetic expression grammar definition.
namespace ArithExpr
{
    x3::rule<class expression, ast::expr   > const expression("expression");
    x3::rule<class term,       ast::expr   > const term("term");
    x3::rule<class factor,     ast::operand> const factor("factor");

    auto const expression_def =
        term
     >> *(
             (x3::char_('+') >> term)
           | (x3::char_('-') >> term)
         );
    auto const term_def =
        factor
     >> *(
             (x3::char_('*') >> factor)
           | (x3::char_('/') >> factor)
         );
    auto const factor_def =
        x3::double_
     | '(' >> expression >> ')'
     | (x3::char_('-') >> factor)
     | (x3::char_('+') >> factor);

    BOOST_SPIRIT_DEFINE(expression, term, factor);

    auto calc = expression;
} // namespace ArithExpr

template <typename Iterator>
double CalcArithExpr(Iterator const &first, Iterator last) {
    ast::expr expr;
    // Build AST.
    if (!x3::phrase_parse(first, last, ArithExpr::calc, x3::ascii::space, expr)) {
        throw std::runtime_error("Cannot parse arithmetic expression");
    }

    // Parse the AST and calculate the result.
    // hana::fix allows recursive lambda call
    auto astEval = hana::fix([](auto self, auto expr) -> double {
        // hana::overload calls a lambda corresponding to the type in the variant
        return hana::overload(
            [](ast::nil) -> double {
                BOOST_ASSERT(0);
                return 0;
            },
            [](double x) -> double { return x; },
            [&](ast::signed_ const &x) -> double {
                double rhs = boost::apply_visitor(self, x.operand_);
                switch (x.sign) {
                    case '-': return -rhs;
                    case '+': return +rhs;
                }
                BOOST_ASSERT(0);
                return 0;
            },
            [&](ast::expr const &x) -> double {
                return std::accumulate(
                    x.rest.begin(), x.rest.end(),
                    // evaluate recursively left-hand side
                    boost::apply_visitor(self, x.first),
                    [&](double lhs, const ast::operation &op) -> double {
                        // evaluate recursively right-hand side
                        double rhs = boost::apply_visitor(self, op.operand_);
                        switch (op.operator_) {
                            case '+': return lhs + rhs;
                            case '-': return lhs - rhs;
                            case '*': return lhs * rhs;
                            case '/': return lhs / rhs;
                        }
                        BOOST_ASSERT(0);
                        return 0;
                    }
                );
            }
        )(expr);
    });

    return astEval(expr);
}

int main(int argc, char *argv[]) {
    auto expr = std::string{"-(4.5 + 5e-1) * 2.22 - 9.1 / 3.45"};
    std::cout << CalcArithExpr(expr.begin(), expr.end()) << std::endl;
}

It calculates -(4.5 + 5e-1) * 2.22 - 9.1 / 3.45 and outputs -13.7377.

Update

Here are instructions how to build bcp and copy selected headers on Windows. Though, without any guarantee. In Linux everything just works, on Windows it is always jumps over some hoops, and the direction of jumps are always unpredictable.

This being said, open PowerShell command line. There

Import-Module 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\Common7\Tools\Microsoft.VisualStudio.DevShell.dll'
Install-Module VSSetup -Scope CurrentUser
Get-VSSetupInstance

Substitute 2019 above with your version of VS. You have to do it only once for your PowerShell. The rest is every time you need to build bcp. Get-VSSetupInstance above will print information about the instances of Visual Studio you have on your machine. Write down InstanceId that you would like to use. Now change to the boost directory in the PowerShell, and:

Enter-VsDevShell InstanceId -DevCmdArguments '-arch=x64' -SkipAutomaticLocation

Where InstanceId is the ID you got from Get-VSSetupInstance. Then from the same command prompt

cd tools\build\src\engine
& .\build.bat
cd ..\..\..\bcp
..\build\src\engine\b2 address-model=64
cd ..\..
dist\bin\bcp fusion\include hana\functional spirit\home\x3 X:\some\path\boost
Benefic answered 19/2, 2021 at 23:51 Comment(6)
hi, thank you very much for your answer. I am very sorry if I let you down, but I don't think I quite get it :< Since I am using Windows 10, I downloaded the .7z file, and extracted it. Then followed your step, and I clicked on the file build.sh; it blinked for a second and then disappeared :< I also clicked on build.bat; but it also blinked for a second then closed itself. And I cannot find the b2 folder as mentioned. I really appreciate your help. Should I change any steps if I am working on Windows? :(Laveen
Ah I think I get what you say; I just don’t know how to change it into Windows language. i just don’t know what’s the build.sh is for. Basically, I just want to pull every needed depedencies from hana and spirit variant into a folder, then only include the header in the code, and that’ll do the trick right? But how am I going to do it in Windows 10? :< and what does the block of code above main() do? I thought I just include the header, and then use it? :< I am sorry for being so naive, I am pretty new to C++ :< thank you very much in advance :”)Laveen
Got you. Sorry for misunderstanding. Spirit is a library that allows you to build a parser of any input language you want, so building an arithmetic parser requires some work of explaining to Spirit what an arithmetic expressions is and how to calculate the result. If you need a library that was specifically made for arithmetic expressions, you should use something like partow.net/programming/exprtk/index.html.Benefic
thank you so much for your help. I think I get it now. Unfortunately exprtk seems to have so many sections and my compiler just hangs when it compiles the header file :< reading all the benchmark, it seems exprtk handles math string very well. Thank you so much, I did post a whole post asking SO about which dll to use, and how to actually “use”/“build” the dlls; but my post just got locked for being so general and not to the point. How can I be “to the point” when I don’t know what point it is? :< You are very nice, my major is math, so if you need any help, I’ll be glad to help u :*Laveen
And I also think exprtk is overkill for my small project. So I decide to write my own parser then. Maybe it’s not very efficient, but loading a 1MB+ header file seems too much for my compiler =)))) thank you so much for ur time :* u r so nice :* If u need any help in math, just come to mathSE, although I may not be so active there nowadays, if by any chance, we meet, I’ll be more than welcome to help you :* Thanks :*Laveen
I still would recommend Spirit then to write your own parser. I updated instructions for Windows if you want to try. I also updated the bcp command line, so now it copies only headers, and a very small portion of them. Yeah, I'll take on your promise with math. :)Benefic

© 2022 - 2024 — McMap. All rights reserved.