[C con Clase] Funciones trigonometricas en char

Steven R. Davidson vze266ft en verizon.net
Lun Feb 25 18:31:59 CET 2008


Hola Francisco,

Francisco Mota wrote:
> Saludos amigos de la lista!!!!
>  
> Me eh topado con el siguiente problema que llevo ya algunos dias sin 
> resolver.
> Estoy trabajando con el WinAPI y el programa da al usuario un area de 
> texto para escribir funciones trigonometricas a evaluar, estas funciones 
> las guardo en una varariable char funcion[50]. Asi que mi problema es el 
> siguiente:
>  
> ¿Como puedo obtener un resultado del tipo double, de la variable char si 
> por ejemplo dentro de la variable char tiene:
>  
> funcion="(45*sin(x+y))/16+cos(tan(x/y)))";  (tomar en cuenta que no 
> siempre va a tener este valor)
>  
> donde x, y son variables del tipo int, que el usuario tambien escoge?.
>  
> Espero que me puedan ayudar, ademas se que este problema mas que ser del 
> API es de C, asi que si alguien me puede echar la mano con un programa 
> en C se lo voy a agradecer, y si no es mucha molestia y me lo explican 
> pues aun mas agradecido quedare.
>  

Veamos. En primer lugar no se trata de una variable 'char', sino de una
cadena de caracteres. Es mejor tener claros estos conceptos para
proseguir con el resto del análisis del problema. En cuanto al proyecto
que tienes entre manos, debo decir que me produce cierto asombro porque
justamente estoy haciendo exactamente lo mismo que has expuesto.

La solución general es interpretar carácter por carácter en la cadena
introducida. Para poder interpretar correctamente la información que
vamos aceptando, necesitamos tener bien claro lo que es aceptable y cómo
interactúa cada "símbolo" con los demás en la expresión introducida.
Usando el ejemplo anterior, tenemos la siguiente separación:

[símbolo,"("] [número,45] [función,"sin"] [símbolo,"("] [variable,"x"]
[operador,"+"] [variable,"y"] [símbolo,")"] [símbolo,")"] [operador,"/"]
[número,16] [operador,"+"] [función,"cos"] [símbolo,"("] [función,"tan"]
[símbolo,"("] [variable,"x"] [operador,"/"] [variable,"y"] [símbolo,")"]
[símbolo,")"] [símbolo,")"]

Como el usuario puede escribir cualquier tipo de expresión, necesitarás
describir precisamente lo que es sintáctica y semánticamente correcto.
Esto significa que tendrás que describir tu lenguaje para escribir
expresiones aritméticas correctas.

Dicho lo anterior, solemos representar las reglas (o axiomas)
sintácticas usando BNF (Forma o Nomenclatura de Backus-Naur). Por ejemplo,

(*) <expresión> ::= <número> | <número> <op_bin> <expresión> |
                     ( <expresión> ) | <op_unit> <expresión>
<signo>   ::= + | -
<op_bin>  ::= + | - | * | /
<op_unit> ::= <signo> | <función>
<función> ::= sin | cos | tan

<número_simple> ::= <entero> | <entero> . | . <entero_simple> |
                     <entero> . <entero_simple>
<número> ::= <número_simple> | <número_simple> <exponente>
<exponente> ::= e <entero> | E <entero>
<entero_simple> ::= <dígito> | <dígito> <entero_simple>
<entero> ::= <entero_simple> | <signo> <entero_simple>
<dígito> ::= 0 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

(*) Empezaríamos por <expresión> para determinar si la expresión
introducida pertenece a nuestro lenguaje aquí descrito a través de
varios axiomas.

Para implementar este análisis lexicográfico, podríamos crear funciones
por cada símbolo, categoría, o "no terminal" que hemos creado. Sin
embargo, sugiero cambiar la descripción del lenguaje en BNF a EBNF (BNF
Extendida). Por ejemplo,

(*) <expresión> ::= <número> [[<op_bin> | <op_unit>] <expresión>] |
                     ( <expresión> )
<signo>   ::= + | -
<op_bin>  ::= + | - | * | /
<op_unit> ::= <signo> | <función>
<función> ::= sin | cos | tan

<número> ::= <número_simple> [<exponente>]
<exponente> ::= [e | E] <entero>

<entero_simple> ::= {<dígito>}
<entero> ::= [<signo>] <entero_simple>

Los metasímbolos [] significan un caso optativo, [ | ] es una selección,
y {} implica una o varias repeticiones.

Volviendo a la implementación, haríamos algo así

bool signo( const char *&pInicio )
{
   if( '+' == *pInicio || '-' == *pInicio )
   {
     ++pInicio;
     return true;
   }

   return false;
}

bool op_bin( const char *&pInicio )
{
   if( '+' == *pInicio || '-' == *pInicio ||
       '*' == *pInicio || '/' == *pInicio )
   {
     ++pInicio;
     return true;
   }

   return false;
}

bool funcion( const char *&pInicio )
{
   if( !strncmp(*pInicio,"sin") || !strncmp(*pInicio,"cos") ||
       !strncmp(*pInicio,"tan")  )
   {
     pInicio += 3;
     return true;
   }

   return false;
}

bool op_unit( const char *&pInicio )
{
   const char *pAux = pInicio;
   if( signo(pInicio) || funcion(pInicio) )
     return true;

   pInicio = pAux;
   return false;
}

Y así sucesivamente. Puedes realizar otras optimizaciones como creas 
conveniente. Además de comprobar que la entrada sea correcta, puedes 
interpretar tales caracteres y convertirlos a tipos de datos 
correspondientes.

También tenemos que tener en cuenta las reglas semánticas. Por ejemplo, 
las operaciones de multiplicación y división deben evaluarse antes de 
las operaciones de suma y resta, pero posteriormente a los paréntesis.

Para poder evaluar esta serie de estructuras de información, existen 
varias formas de hacerlo. Una forma es usando una lista dinámicamente 
enlazada. Luego convertimos esta expresión infija a posfija, a través de 
una pila. A la hora de evaluar esta expresión posfija, usamos otra pila 
que contiene solamente números. Para más información, puedes consultar 
el artículo titulado "Crear una calculadora Simple" en nuestra página. 
El enlace es: http://articulos.conclase.net/calculadora/simple.html

Otra forma es usando un árbol binario. En lugar de tener una lista 
(lineal) de símbolos, tenemos una estructura con ciertas propiedades que 
nos proporcionan ventajas no solamente a la hora de organizar los 
símbolos sin a la hora de evaluar la expresión. El árbol vendría a ser 
el siguiente:

"(45*sin(x+y))/16+cos(tan(x/y)))"

           (+)
          /   \
        /       \
      (/)      (cos)
     /   \      /
   (*)   16  (tan)
  /  \        / \
45 (sin)   "x" "y"
     / \
   "x" "y"

Como puedes ver, en este árbol he decidido no agregar los paréntesis, ya 
que sólo sirven para agrupar subexpresiones y aumentaría la ramificación 
con más niveles en el árbol. Para evaluar este árbol, sólo tenemos que 
recorrerlo en "inorden". Al final del recorrido, obtendremos el 
resultado final de la expresión.

Creo que es obvio, pero lo comentaré para ceciorarme. Tendrás que 
sustituir las variables "x" e "y" con valores concretos, antes o 
mientras realizas la evaluación.

Existen más detalles sobre estos temas de lingüística, pero creo que me 
he extendido lo suficiente en este mensaje.


Espero que todo esto te ayude.

Steven






Más información sobre la lista de distribución Cconclase