My answer will not be in C, but I hope to help in a possible implementation in this language. ;)
In most compilable languages, either in "machine code" or bytecode, there is this problem related to the interpretation of formulas or functions of variables.
At the time of college I did an interpreter of mathematical functions with variables in Delphi for the subject of operational research and used it in several optimization algorithms where you could type a function and the program found the maximum point(s) and/or minimum(s) of it. It’s not as complicated as it sounds. Too bad you don’t have the source code on hand now.
Some time later, already working with Java, I made another implementation of a class to facilitate the interpretation of simple expressions. I never got to use in production, so ended up staying in the prototype phase, that is, it has not been extensively tested, is subject to bugs and only supports the most basic mathematical operations. In addition, this implementation was made for Java 1.4, so it also does not use several new language features. I’m sure this code could be much better, but it might help to create a C version++.
import java.math.BigDecimal;
import java.text.MessageFormat;
/**
* Calcula o resultado de uma dada expressão, de acordo com os coeficientes definidos.
*
* A expressão é uma fórmula matemática válida, podendo conter os coeficiente (variáveis),
* operadores ("+", "-", "*", "/" e "^" -> adição, subtração, multiplicação, divisão e
* potenciação) e parêntesis.
*
* Os coeficientes podem ser refenciados na fórmula pelo índice do vetor entre chaves {n}
* ou pelas letras de "A" a "Z", sendo "A" equivalente ao índice 0 (zero) do vetor.
*
* Exemplos: "({0} + {1} + {2}) - {3}" ou "(A + B) / (C - D) ^ 2"
*
* Precedência de Operadores (quando não há parêntesis):
* 1. Potenciação
* 2. Multiplicação e Divisão
* 3. Adição e subtração
*/
public class Expressao {
private static int TIPO_NUMERO = 1;
private static int TIPO_OPERADOR = 2;
private static int TIPO_PONTO = 3;
private static int TIPO_LETRA_AZ = 4;
private static int TIPO_CHAVE_ABRIR = 5;
private static int TIPO_CHAVE_FECHAR = 6;
private static int TIPO_PARENTESIS_ABRIR = 7;
private static int TIPO_PARENTESIS_FECHAR = 8;
private static String parentesisFaltando = "Parêntesis faltando a partir da posição {0}!";
private static String valorEsperado = "Coeficiente ou número esperado na posição {0}!";
private static String numeroEsperado = "Número esperado na posição {0}!";
private static String indiceEsperado = "Índice de coeficiente esperado na posição {0}!";
private static String chaveEsperada = "Chave de fechamento esperada na posição {0}!";
private static String divisaoPorZero = "Divisão por zero na posição {0}!";
private static String operadorEsperado = "Operador esperado na posição {0}!";
private static String indiceInvalido = "Índice de coeficiente inválido na posição {0}!";
private int posExpressao;
private int tipoElemento;
private char letra;
private String expressao;
private BigDecimal[] coeficientes;
/**
* Atalho para execução alternativa
*/
public static BigDecimal calcular(String expressao, BigDecimal[] coeficientes) {
try {
Expressao exp = new Expressao(expressao, coeficientes);
return exp.calcular();
} catch (Exception e) {
LogLE.logError(e);
return Calc.ZERO;
}
}
/**
* Atalho para execução alternativa
*/
public static BigDecimal calcular(String expressao, String[] coeficientes) {
try {
Expressao exp = new Expressao(expressao, coeficientes);
return exp.calcular();
} catch (Exception e) {
return Calc.ZERO;
}
}
/**
* Atalho para execução alternativa
*/
public static BigDecimal calcular(String expressao, Object[] coeficientes) {
try {
Expressao exp = new Expressao(expressao, coeficientes);
return exp.calcular();
} catch (Exception e) {
LogLE.logError(e);
return Calc.ZERO;
}
}
/**
* Constrói um avaliador para a expressão e respectivos coeficientes (variáveis)
*
* Exemplo: new Expressao("(A + B + C) - D", new BigDecimal[] {v1, v2, v3, v4}
*/
public Expressao(String expressao, BigDecimal[] coeficientes) throws Exception {
this.expressao = expressao.replaceAll("\\s", "").toUpperCase();
this.coeficientes = coeficientes;
this.posExpressao = -1;
}
/**
* Constrói um avaliador para a expressão e respectivos coeficientes (variáveis)
*
* Exemplo: new Expressao("({0} + {1} + {2}) - {3}", new String[] {s1, s2, s3, s4}
*/
public Expressao(String expressao, String[] coeficientes) throws Exception {
this.expressao = expressao.replaceAll("\\s", "").toUpperCase();
this.coeficientes = new BigDecimal[coeficientes.length];
for (int i = 0; i < coeficientes.length; i++) {
this.coeficientes[i] = Calc.toBigDecimal(coeficientes[i]);
}
this.posExpressao = -1;
}
/**
* Constrói um avaliador para a expressão e respectivos coeficientes (variáveis)
* Os coeficientes podem ser String, BigDecimal, Integer ou Double
*
* Exemplo: new Expressao("({0} + {1} + {2}) - {3}", new Object[] {o1, o2, o3, o4}
*/
public Expressao(String expressao, Object[] coeficientes) throws Exception {
this.expressao = expressao.replaceAll("\\s", "").toUpperCase();
this.coeficientes = new BigDecimal[coeficientes.length];
for (int i = 0; i < coeficientes.length; i++) {
if (coeficientes[i] == null) {
this.coeficientes[i] = Calc.ZERO;
} else if (coeficientes[i] instanceof String) {
this.coeficientes[i] = Calc.toBigDecimal((String) coeficientes[i]);
} else if (coeficientes[i] instanceof BigDecimal) {
this.coeficientes[i] = Calc.toBigDecimal((BigDecimal) coeficientes[i]);
} else if (coeficientes[i] instanceof Double) {
this.coeficientes[i] = Calc.toBigDecimal(((Double) coeficientes[i]).doubleValue());
} else if (coeficientes[i] instanceof Integer) {
this.coeficientes[i] = Calc.toBigDecimal(((Integer) coeficientes[i]).intValue());
} else {
//tenta converter o objeto para String e depois para BigDecimal
this.coeficientes[i] = Calc.toBigDecimal(coeficientes[i].toString());
}
}
this.posExpressao = -1;
}
//retorna verdadeiro se o próximo caracter for o início de um valor válido com ou sem sinal
private boolean ehValorSinal() {
return tipoElemento == TIPO_NUMERO || tipoElemento == TIPO_CHAVE_ABRIR || tipoElemento == TIPO_PARENTESIS_ABRIR ||
(tipoElemento == TIPO_OPERADOR && (letra == '+' || letra == '-') || tipoElemento == TIPO_LETRA_AZ);
}
/**
* Avalia a expressão de acordo com os coeficientes definidos e retorna o resultado
*/
public BigDecimal calcular() throws Exception {
BigDecimal resposta = Calc.ZERO;
proximoElemento();
if (!EOF()) {
if (!ehValorSinal()) {
Erro(valorEsperado);
}
resposta = expressaoPrecedencia();
}
while (!EOF()) {
if (tipoElemento == TIPO_OPERADOR) {
char operador = letra;
proximoElemento();
if (!ehValorSinal()) {
Erro(valorEsperado);
}
BigDecimal outroValor = expressaoPrecedencia();
if (operador == '+') {
resposta = Calc.soma(resposta, outroValor);
} else if (operador == '-') {
resposta = Calc.subtrai(resposta, outroValor);
}
} else {
Erro(operadorEsperado);
}
}
return resposta;
}
//avalia uma expressão com precedência 1, atualmente multiplicação e divisão (analisador sintático)
private BigDecimal expressaoPrecedencia() throws Exception {
BigDecimal resposta = expressaoPrecedencia2();
while (!EOF() && (tipoElemento == TIPO_OPERADOR && (letra == '*' || letra == '/'))) {
char operador = letra;
proximoElemento();
if (ehValorSinal()) {
BigDecimal outroValor = expressaoPrecedencia2();
if (operador == '*') {
resposta = Calc.multiplica(resposta, outroValor);
} else if (operador == '/') {
if (Calc.ehZero(outroValor)) {
Erro(divisaoPorZero);
}
resposta = Calc.divide(resposta, outroValor);
}
}
}
return resposta;
}
//avalia uma expressão com precedência 2, atualmente a potenciação (analisador sintático)
private BigDecimal expressaoPrecedencia2() throws Exception {
BigDecimal resposta = valorSinal();
while (!EOF() && (tipoElemento == TIPO_OPERADOR && letra == '^')) {
char operador = letra;
proximoElemento();
if (ehValorSinal()) {
BigDecimal outroValor = valorSinal();
if (operador == '^') {
resposta = Calc.potencia(resposta, outroValor);
}
}
}
return resposta;
}
//avalia um valor válido na expressão com ou sem um operador unitário (analisador sintático)
private BigDecimal valorSinal() throws Exception {
//operador unitário
if (tipoElemento == TIPO_OPERADOR && (letra == '+' || letra == '-')) {
char operadorUnitario = letra;
proximoElemento();
BigDecimal valor = valor();
if (operadorUnitario == '-') {
valor = Calc.multiplica(valor, -1);
}
return valor;
} else {
return valor();
}
}
//avalia um valor válido na expressão: {n}, 9.99, 9.99, (...), A (analisador sintático)
private BigDecimal valor() throws Exception {
if (tipoElemento == TIPO_PARENTESIS_ABRIR) {
int numParentesis = 1;
int posIni = posExpressao + 1;
do {
proximoElemento();
if (letra == '(') {
numParentesis++;
} else if (letra == ')') {
numParentesis--;
}
} while (numParentesis > 0 && posExpressao < expressao.length());
if (posExpressao >= expressao.length()) {
Erro(parentesisFaltando);
} else {
proximoElemento();
Expressao exp = new Expressao(Texto.cortar(expressao, posIni, posExpressao - posIni - 1), coeficientes);
return exp.calcular();
}
} else if (tipoElemento == TIPO_CHAVE_ABRIR) {
//coeficiente
proximoElemento();
if (EOF() || tipoElemento != TIPO_NUMERO) {
Erro(indiceEsperado);
}
int indice = numeroInteiro();
if (EOF() || tipoElemento != TIPO_CHAVE_FECHAR) {
Erro(chaveEsperada);
}
if (indice >= coeficientes.length || indice < 0) {
Erro(indiceInvalido);
}
proximoElemento();
return Calc.toBigDecimal(coeficientes[indice]);
} else if (tipoElemento == TIPO_NUMERO) {
//número
return numeroReal();
} else if (tipoElemento == TIPO_LETRA_AZ) {
int indice = letra - 'A';
if (indice >= coeficientes.length || indice < 0) {
Erro(indiceInvalido);
}
proximoElemento();
return Calc.toBigDecimal(coeficientes[indice]);
}
Erro(valorEsperado);
return null;
}
//avalia um número real no formato 9.99 (analisador sintático)
private BigDecimal numeroReal() throws Exception {
String numero = numeroTexto();
if (!EOF() && tipoElemento == TIPO_PONTO) {
proximoElemento();
if (!EOF() && tipoElemento == TIPO_NUMERO) {
numero += "," + numeroTexto();
} else {
Erro(numeroEsperado);
}
}
return Calc.toBigDecimal(numero);
}
//avalia um número inteiro (analisador sintático)
private int numeroInteiro() {
return Integer.parseInt(numeroTexto());
}
//avalia uma sequência de caracteres numéricos (analisador sintático)
private String numeroTexto() {
String num = new String(new char[] {letra});
do {
proximoElemento();
if (!EOF() && tipoElemento == TIPO_NUMERO) {
num += letra;
}
} while (!EOF() && tipoElemento == TIPO_NUMERO);
return num;
}
//analisador léxico
private void proximoElemento() {
if (posExpressao < expressao.length() - 1) {
letra = expressao.charAt(++posExpressao);
} else {
posExpressao++;
letra = 0;
}
tipoElemento = 0;
switch (letra) {
case '{':
tipoElemento = TIPO_CHAVE_ABRIR;
break;
case '}':
tipoElemento = TIPO_CHAVE_FECHAR;
break;
case '(':
tipoElemento = TIPO_PARENTESIS_ABRIR;
break;
case ')':
tipoElemento = TIPO_PARENTESIS_FECHAR;
break;
case '.':
tipoElemento = TIPO_PONTO;
break;
case '+':
case '-':
case '*':
case '/':
case '^':
case '%':
tipoElemento = TIPO_OPERADOR;
break;
default:
if (letra >= 'A' && letra <= 'Z') {
tipoElemento = TIPO_LETRA_AZ;
} else if (letra >= '0' && letra <= '9') {
tipoElemento = TIPO_NUMERO;
}
break;
}
}
//verifica se chegou ao final da expressão
private boolean EOF() {
return posExpressao >= expressao.length() ;
}
//lança um erro (Exception com descrição) quando encontrar qualquer problema na avaliação da expressão
private void Erro(String mensagem) throws Exception {
throw new Exception(MessageFormat.format(mensagem, new Object[] { Calc.imprimeInt(posExpressao) }));
}
//rotinas de inicialização de vetor com Strings
public static BigDecimal[] getVetor(String v1, String v2) {
return new BigDecimal[] {Calc.toBigDecimal(v1), Calc.toBigDecimal(v2)};
}
public static BigDecimal[] getVetor(String v1, String v2, String v3) {
return new BigDecimal[] {Calc.toBigDecimal(v1), Calc.toBigDecimal(v2), Calc.toBigDecimal(v3)};
}
public static BigDecimal[] getVetor(String v1, String v2, String v3, String v4) {
return new BigDecimal[] {Calc.toBigDecimal(v1), Calc.toBigDecimal(v2), Calc.toBigDecimal(v3), Calc.toBigDecimal(v4)};
}
public static BigDecimal[] getVetor(String v1, String v2, String v3, String v4, String v5) {
return new BigDecimal[] {Calc.toBigDecimal(v1), Calc.toBigDecimal(v2), Calc.toBigDecimal(v3),
Calc.toBigDecimal(v4), Calc.toBigDecimal(v5)};
}
public static BigDecimal[] getVetor(String v1, String v2, String v3, String v4, String v5, String v6) {
return new BigDecimal[] {Calc.toBigDecimal(v1), Calc.toBigDecimal(v2), Calc.toBigDecimal(v3),
Calc.toBigDecimal(v4), Calc.toBigDecimal(v5), Calc.toBigDecimal(v6)};
}
//com BigDecimals
public static BigDecimal[] getVetor(BigDecimal v1, BigDecimal v2) {
return new BigDecimal[] {v1, v2};
}
public static BigDecimal[] getVetor(BigDecimal v1, BigDecimal v2, BigDecimal v3) {
return new BigDecimal[] {v1, v2, v3};
}
public static BigDecimal[] getVetor(BigDecimal v1, BigDecimal v2, BigDecimal v3, BigDecimal v4) {
return new BigDecimal[] {v1, v2, v3, v4};
}
public static BigDecimal[] getVetor(BigDecimal v1, BigDecimal v2, BigDecimal v3, BigDecimal v4, BigDecimal v5) {
return new BigDecimal[] {v1, v2, v3, v4, v5};
}
public static BigDecimal[] getVetor(BigDecimal v1, BigDecimal v2, BigDecimal v3, BigDecimal v4, BigDecimal v5, BigDecimal v6) {
return new BigDecimal[] {v1, v2, v3, v4, v5, v6};
}
//com Objects
public static Object[] getVetor(Object v1, Object v2) {
return new Object[] {v1, v2};
}
public static Object[] getVetor(Object v1, Object v2, Object v3) {
return new Object[] {v1, v2, v3};
}
public static Object[] getVetor(Object v1, Object v2, Object v3, Object v4) {
return new Object[] {v1, v2, v3, v4};
}
public static Object[] getVetor(Object v1, Object v2, Object v3, Object v4, Object v5) {
return new Object[] {v1, v2, v3, v4, v5};
}
public static Object[] getVetor(Object v1, Object v2, Object v3, Object v4, Object v5, Object v6) {
return new Object[] {v1, v2, v3, v4, v5, v6};
}
}
I think that’s the simplest and least efficient interpreter there is. I don’t remember exactly the theory, but I learned in the compiler class in college and I always brought a cost-effective code in the sense that it’s simple to implement and meets simpler requirements. That is, it is not good to implement an HP12C (if you want one, go to site of @epx), or calculations for the stock exchange.
This technique basically consists of implementing a parser with a method for each grammar rule, from the most complex to the simplest.
If anyone has anything to add, feel free to comment.
I think that I C doesn’t have
eval
, and that you need to basically build a parser of mathematical expressions.– bfavaretto
In pure C there is nothing, or you will have to write your own library, which is something really interesting, worth learning, or looking for some library ready. Google suggested this one: http://expreval.sourceforge.net
– C. E. Gesser
This is one of the "n" reasons why low-level language (C, C++) is not used except when very necessary. There’s always the need to interpret something and it’s always trouble. What many programs do is incorporate an interpreter of a more or less established language (Javascript, Python, Lua) to become "Scriptable" and/or interpret high-level structures more easily.
– epx
@epx Interpreted languages have the facility to rely on their own interpreter to solve expressions. But if the type of expression you want to evaluate does not use the same language syntax, the problem would be the same.
– C. E. Gesser
@epx Also, run
eval
on something typed by the user is at least dangerous.– Guilherme Bernal
@epx, I would already say the opposite: using a language interpreted only by "Eval" can lead to very large insecurity situations in the system. Using Eval in user-typed text is very dangerous and can lead to code Injection: http://en.wikipedia.org/wiki/Code_injection#Dynamic_evaluation_vulnerabilities.
– user568459
@Guilhermebernal did not speak at any time of using "Eval". I talked about interpreting data, whether XML, JSON, a mathematical expression, a configuration file... all of this always appears in a system of sufficiently large size. " Any sufficiently complicated C or FORTRAN program contains a improvised, poorly specified, bugle and slow implementation of half of Common Lisp."
– epx
eval
, my dear friend, it’s just notevil
because of a letter. It really can be evil, but hackers/Ammers love it :)– Paulo Roberto Rosa
@epx, there are libraries already with high level of development and well established for C that interpret all these types of data that you cited. If the programmer prefers to do it alone instead of searching a library on the Internet, the language is not to blame...
– user568459