venerdì 26 ottobre 2007

Virgola mobile e precisione

Sicuramente vi è capitato almeno una volta nella vostra vita programmativa di lavorare con tipi in virgola mobile. Ache a me è capitato, ma sinceramente mai ho fatto caso a una cosa che in prima apparenza sembrava stranissima, poi inrealtà si è verificata normale.
Provate a scrivere il seguente frammento di codice in una classe c#:
Single c = 12;
Single d = 12.00f;
Single de = 12.23f;
Single r = 12.1f;

se poi fate visualizzare a schermo i valori, vengono visualizzati correttamente, ossia
System.Console.WriteLine(c.ToString());
System.Console.WriteLine(d.ToString());
System.Console.WriteLine(de.ToString());
System.Console.WriteLine(r.ToString());

Ottenete esattamente i valori che vi interessano.

Provate però a fare il debug passo passo e fermarvi su ogni singolo valore:

Beh certo i valori non sono quelli che avete inserito voi. Ma da cosa dipende questa cosa?

DA MSDN:
"Single-precision numbers store an approximation of a real number
When you work with floating-point numbers, keep in mind that they do not always have a precise representation in memory"

Diamo due articoli come lettura su questo argomento:

http://msdn2.microsoft.com/en-us/library/system.single.aspx
http://msdn2.microsoft.com/en-us/library/system.single(VS.80).aspx
presi direttamente da MSDN, la Bibbia della programamzione in .NET

Il problema fondamentale sta nel fatto che il Single (float), come il Double (double) sono tipi di dato a virgola mobile. Ciò vuol dire che non hanno una precisa rappresentazione all'interno del computer, perchè come si dovrebbe ben sapere, tra un numero e l'altro ne esistono infiniti e infinito putroppo :( è un valore che il computer non capisce. La virgola mobile allora (come del resto anche la virgola fissa) cerca di approssimare questo numero a un determinato numero di cifre decimali: float 6, double 15. Se ci facciamo caso infatti i valori visualizzati dal debug sono 12.0 che è corretto poichè è preciso 12.2299995 che approssimato viene 12.23 infatti l'approssimazione viene fatta sull'ultima cifra decimale che in questo caso è la settima e il Single ne contiene solo 6 quindi si può dire con ottima approssimazione che il numero registrato è 12.23 dato che per il calcolatore quello è il più vicino numero registrabile a quello reale. Ad esempio se inseriremmo 12.229999 il computer avrà sicuramente in memoria 12.2299990.

Ma come mai il computer si complica così la vita? Non potrebbe stare semplicemente a registrare il valore che io gli inserisco senza fare a modo suo per registrare? Beh la risposta risiede nel metodo di immagazzinamento a virgola mobile che viene usato nel compilatore in base 2.

Da Wikipedia:
floating-point
Un generico numero reale a può così essere rappresentato come (si indica con le lettere maiuscole il significato aritmetico dei campi):

Questo metodo di scrittura permette di rappresentare un larghissimo insieme numerico all'interno di un determinato numero di cifre, cosa che la virgola fissa non concede.

In generale, questo tipo di numeri si comporta in modo molto simile ai numeri reali. Tuttavia ciò porta spesso i programmatori a non considerare l'importanza di un'adeguata analisi numerica risultati ottenuti. Ci sono molte incongruenze tra il comportamento dei numeri in virgola mobile in base 2 impiegati nell'informatica e quello dei numeri reali, anche in casi molto semplici (ad esempio la frazione 0,1 che non può essere rappresentata da nessun sistema binario in virgola mobile). Per questo non è un formato impiegato ad esempio in campo finanziario.

Le cause principali di errore nel calcolo in virgola mobile sono:

  • arrotondamento
    • numeri non rappresentabili (ad esempio 0,1);
    • arrotondamento di operazioni aritmetiche (es.: 2/3=0,666667);
  • assorbimento (es.: 1×1015 + 1 = 1×1015);
  • cancellazione (es.: sottrazione di due numeri molto vicini);
  • overflow (con segnalazione di risultato infinito);
  • underflow (dà come risultato 0, un numero subnormale o il più piccolo numero rappresentabile);
  • operazioni impossibili (es.: radice quadrata di un numero negativo diverso da zero, danno come risultato Nan);
  • errori di arrotondamento: a differenza della virgola fissa, l'impiego del dithering sulla virgola mobile è pressoché impossibile.

La virgola mobile appare più appropriata quando si richiede una certa precisione relativa al valore. Quando è richiesta una precisione assoluta, la virgola fissa sembra una scelta migliore.


Riguardo all'errore di precisione che provoca l'utilizzo della virgola mobile, innanzitutto notiamo che se x è il numero rappresentato, cioè fl(x)=segno(x)(0.a1a2...an)bexp(p) allora si avrà 1/b*bexp(p)<=|x|=bexp(1-p) allora 1/|x|<=bexp(p-1) e quindi bexp(p-t)*bexp(1-p)=bexp(1-t). In particolare l'errore relativo è variabile ma sempre al di sotto del valore trovato(che infatti non dipende da p e quindi non dipende dal numero rappresentato, come invece fa l'errore relativo) che viene anche detto precisione macchina!

In conclusione bisogna stare attenti a usare la virgola mobile, ma a meno che non vi servano calcoli con cifre esatte fino alle 154106410489046 posizione decimale, il .NET provvede per voi.

Pagina caricata due volte

Mi è capitata una cosa stranissima: usando una gridview su una pagina asp.net mi sono accorto che quando la pagina veniva richiamata la sua esecuzione veniva ripetuta due volte. Andando a spulciare in quello che era il codice e facendo varie prove, mi sono accorto che inserendo nella griglia una immagine oppure un button immage, sia che sia in una TemplateColoumn o una ButtonColoumn nel debug veniva chiamato 2 volte il Page_load. Cosa stranissima!!! Trova cerca e ritrova su internet sono arrivato alla conclusione che non ero l'unico e non in un unico scenario: non solo succede per la gridview in queste condizioni, ma anche per altre configurazioni che non stiamo qui ad elencare. Per ovviare al problema, bisogna impostare la direttiva della pagina web AutoEventWireup su false.
<%@ Page Language="C#" MasterPageFile="~/MasterPage/Principal.master" AutoEventWireup="false" CodeFile="Default.aspx.cs" Inherits="_Default" Title="Untitled Page"%>
Ora in C# questa direttiva per default viene settata su true, quindi bisogna stare attenti a questa cosa, se necessita di essere controllata. Il problema nasce infatti da questa direttiva che se impostata a true attiva automaticamente alcuni eventi: Come funziona AutoEventWireup se questi eventi non sono collegati a nessun evento (almeno da quello che ho capito io) scatenano un comportamento di default. Impostando la direttiva su false si evita questo problema.

Per maggiori informazioni sul problema cercare su google : asp.net page load twice problem.

Cito inolte questo articolo preso da ASPITALIA che parla "un pò male dell'AutoEventWireUp": AutoEventWireup: se lo conosci lo eviti

mercoledì 24 ottobre 2007

Sensori Biometrici

Sto effettuando degli studi sui sensori biometrici per un programma che mi hanno commissionato, a breve pubblicherò un articolo su come integrare un sensore biometrico all'interno della nostra applicazione C#.
Qualche chicca di preannuncio:
Il sensore Biometrico è della Precise Biometric ed è il piccolo ma imbattibile (tranne dai fratelli più grandi) Precise 100 XS

Applicato in una applicazione C# grazie alle ormai famosissime BioAPI del BioAPIConsortium e il suo wrapper in C#. Ovvio che ha bisogno anche dei driver di casa madre, e la Precise Biometric a dire la verità non ha molto di buono in questo. Infatti fornisce solo il driver del sensore non fornendo l'utilissimo BSP che serve per collegare il sensore alle BioAPI e decisamente io non so dove trovarlo sul web. Io ho contattato il fornitore che gentilmente me lo ha inviato via mail. Purtroppo non sapendo che tipo di licenza ha questo BSP, evito di pubblicarlo almeno per adesso.
Cmq a breve ci saranno aggiornamenti e vi spiegherò come usare tutto il sistemino pubblicando anche il codice.

Spero di suscitare il vostro interesse :)

martedì 23 ottobre 2007

Parser per espressioni matematiche

Almeno una volta nella vita programmativa ci si è posti di fornite all'esigenza di fare anche un piccolo parse di una stringa. Beh questa volta è toccato a me. Ho dovuto creare un parser per espressioni matematiche che a run-time "parsasse" le espressioni e le eseguisse inserendo al posto di alcuni marcatori dei valori passati da programma.

Da prima ho provato a cercare su San Google e ho trovato su Santissimo CodeProject e ho trovato una soluzione che poteva andare ma che non era di mio gradimento, infatti anche se molto performante e molto vicina a quello che realmente è un parser, è molto difficile da "smanettare": infatti fa largo uso di lunghe e qualche volta non del tutto comprensibili Espressioni Regolari. Analizzando il codice ho scoperto che questo parser ti permetteva di inserire funzioni matematiche e di richiamare una serie di funzioni del .NET (Sin, Cos, ecc... la lista completa è su the code project), pecca però per la mia utilità di una sostituzioni di variabili. Provando a mettere insieme qualcosa di concreto ne ho tirato fuori poco o niente.

Mi sono chiesto allora: ci sarà di meglio? Beh se devo dirla tutta di meglio non è (anche perchè il meglio in questo settore è soggettivo rispetto al contesto) ma cmq è qualcosa che si avvicinava molto a quello che interessava a me.

Cercando sempre sui theCodeProject ho trovato un secondo parser per espressioni che però era legato alla sintassi C#. Infatti quest'ultimo prende l'espressione matematica scritta in C# e la esegue creando a run-time un assembly in memoria che esegue l'espressione. Le pecche di questo parser sono ovvie! Nessuna protezione! Io posso girare il codice che mi interessa senza sessuna limitazione, il programma, ne perde in sicurezza. Inoltre, neanche questo parser
permette di inserire delle variabili e quindi bisogna dargli in pasta una espressione bella e formattata.

Ho pensato allora di inserire alcune nuove features in questa nuova versione e di creare un parser matematico che si basasse su quel modello, ma avesse in più: l'inserimento di variabili, una certa indipendenza dal linguaggio C# avvicinandolo il più possibile a quello matematico, l'inserimento di variabili che attraverso collezioni restituissero valori, una certa sicurezza che bloccasse il codice maligno, una robustezza nell'esecuzione.

Ne è venuto fuori questo:

MATH PARSER

Il parser che da ora in poi chiameremo MathParser esegue espressioni matematiche tra le più comuni. Le espressioni possono essere nella prima versione la 1.0.0b solo di tipo algebrico. Le variabili possono essere definite inserendo il nome delle variabili racchiuso tra parentesi quadre (e.g. [valore1]), le funzioni trigonometrie e le più normali funzioni algebriche supportate sono:
addizione +, sottrazione -, moltiplicazione *, divisione /,% modulo, assoluto abs(espressione), arcocoseno acos(espressione), arcoseno asin(espressione), numero intero int(espressione), arrotondamento a n cifre decimali round(espressione,n), radice quadrata sqrt(espressione).
Possono essere inserite le costanti matematiche pi-greco e e (numero di nepero) attraverso l'inserimento delle stringe @pi ed @e rispettivamente. I numeri decimali devono essere divisi dalla loro parte intera tramite la virgola. L'ordine delle partentesi è dettato dal loro ordine nelle espressioni e sono tutte di tipo ( e ).
Ci sono alcuni caratteri che non è possibile inserire quali: . punto, " virgolette, { } parentesi graffe, ? punto interrogativo, : due punti, & e commerciale, | pipe, = uguale, < > minore e maggiore, per ragioni di sicurezza.
Le pecche di questo sistema almeno nella sua prima versione beta sono: non c'è l'elevamento a potenza, non ha un elevatissimo controllo sul codice dell'espressione, è ancora abbastanza legato al linguaggio di programmazione, non ha tutte le funzioni matematiche di base, quando si fanno delle divisioni tra numeri interi, se si vuole fare una divisione reale bisogna inserire i numeri comprensivi di parte decimale anche se zero (e.g. 4/3 deve essere 4.0/3.0), ma rispetto alla sua versione, ... chiamiamola alfa e da cui eredita la maggior parte di questi bug, è abbastanza avanzato e sicuro.

L'implementazione

Passiamo ora a implementare il sistema. Per prima cosa definiamo le classi di servizio che ci servono:

Una classe di eccezione che sarà utilizzata dal parser per lanciare le eccezioni:

using System;
namespace MathParser {
public class MathParserException : Exception {
public MathParserException() {}

public MathParserException(String message) : base(message) {}

public MathParserException(String message, Exception innerException) : base(message, innerException) {}
}
}
[+/-] Listato 1

niente di complicato, abbiamo semplicemente ereditato dalla classe Exception e ricostruito i costruttori che ci interessano.

La seconda classe di servizio è quella che poi verrà ereditata dalla classe dell'espressione:

using System;

namespace MathParser {
public abstract class BaseClass : IDisposable {
public virtual double eval() {
return 0.0;
}

#region IDisposable Membri di
public void Dispose() {}
#endregion
}
}
[+/-] Listato 2

anche qui niente di complicato definiamo una classe che è IDisposable (da implementare) e un metodo virtuale double eval() che ci ritorna 0.0. Questo metodo verrà poi ridefinito quando la classe viene ereditata e viene sostituito dall'espressione.

Bene abbiamo fatto la base del nostro parser, passiamo ora a implementare la nostra ultima classe, che sarà quella principale del parser che creerà l'assembly ed eseguirà l'espressione.

Definiamo qualche using tra cui gli using base System e il conosciuto System.Collection.Generic per le collezioni. I restanti due servono per la creazione dell'assembly e sfruttano il compilatore integrato nel Framework .NET 2.0.

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using Microsoft.CSharp;

e definiamo la classe:

namespace MathParser {
public class MathExpressionParser : IDisposable {

dichiariamo poi alcuni membri per la classe:

#region Membri
//Lista di funzioni di sistema
private static readonly Dictionary functionList = new Dictionary();
//Lista di errori di compilazione
private readonly List listError = new List();
//Espressione da parsare
private String expression = "";
//Oggetto che eseguirà l'espressione di tipo BaseClass precedentemente definito
private BaseClass myobj = null;
//Variabile che indica se l'espressione è stata passata già al parser o no
private bool parsed = false;
//Collezione di valori per le variabili
private Dictionary valori = new Dictionary();
#endregion

[+/-] Listato 3

penso non abbiamo bisogno di ulteriori commenti oltre quelli già inseriti direttamente nel codice.

Inseriamo un costruttore statico che verrà chiamato solo la prima volta che la classe viene istanziata:

#region Costruttori Statici
static MathExpressionParser() {
functionList.Add("abs", "Math.Abs");
functionList.Add("acos", "Math.Acos");
functionList.Add("asin", "Math.Asin");
functionList.Add("int", "Math.Floor");
functionList.Add("round", "Math.Round");
functionList.Add("sqrt", "Math.Sqrt");
functionList.Add("@pi", "Math.PI");
functionList.Add("@e", "Math.E");
//Aggiungere qui altre funzioni di sistema
}
#endregion
[+/-] Listato 4

questo costruttore contiene la lista delle funzioni di sistema che possono essere inserite nell'espressione, come si nota ogni espressione verrà poi trasformata nel suo rispettivo corrispondente in C#. Questo fa capire anche com'è semplice inserire delle funzioni personalizzate all'interno del parser in una nuova features, o completare semplicemente la lista.

Creiamo qualche proprietà che aiuta chi utilizza il Parser:

#region Proprietà
// Lista degli errori dopo il Parse
public List ListError {
get { return listError; }
}

// Restituisce o imposta la lista dei valori
public Dictionary Valori {
get { return valori; }
set {
listError.Clear();
parsed = false;
if(valori == null) valori = new Dictionary();
else valori = value;
}
}

// Restituisce se l'espressione è stata passata al parser
public bool Parsed {
get { return parsed; }
}

// Restituisce o imposta la stringa di espressione
public string Expression {
get { return expression; }
set {
listError.Clear();
parsed = false;
expression = value;
}
}
#endregion
[+/-] Listato 5

Come possiamo vedere le uniche due proprietà settabili sono Expression e Valori, che a loro volta azzerano il parse se settate ponendo parsed=false. Le restanti proprietà sono opportunamente commentate.

Vengono i metodi privati. Questi sono particolarmente importanti poichè permettono di trasformare l'espressione passata al Parser in una espressione completamente comprensibile al C# (ovviamente se scritta correttamente).



#region Metodi privati
// Sostituisce le funzioni con quelle di sistema

Espressione in cui sostituire i valori della function di Sistema
// Ritorna la stringa filtrata
private static string sobstituteSystemFunction(String expr) {
Dictionary.Enumerator c = functionList.GetEnumerator();
while (c.MoveNext()) {
KeyValuePair kvp = c.Current;
expr = expr.Replace(kvp.Key.ToLower(), kvp.Value.Replace(",", "."));
}
return expr;
}

//
// Sostituisce i marcatori con i valori
//
//

Espressione in cui sostituire i marcatori con i valori
// Ritorna la stringa filtrata
private string sobstitute(String expr) {
Dictionary.Enumerator c = Valori.GetEnumerator();
while (c.MoveNext()) {
KeyValuePair kvp = c.Current;
expr = expr.Replace("[" + kvp.Key.ToLower() + "]", kvp.Value.ToString().Replace(",", "."));
}
return expr;
}

//
// Filtro anti hack. Filtra tutti i caratteri o le stringhe che potrebbero essere usate come codice non desiderato
//
//

Espressione da filtrare
// Espressione filtata
private static string filter(String expr) {
if(expr.Contains(".")) throw new MathParserException("I numeri decimali devono essere separati con la virgola (,)");
if(expr.Contains("{") || expr.Contains("}")) throw new MathParserException("Non sono ammesse parentesi {, solo parentesi tonde per priorità alle espressioni e quadre per identificare i marcatori");
if(expr.Contains("=") || expr.Contains("&amp;gt;") || expr.Contains("&amp;gt;") || expr.Contains("!") || expr.Contains("\"") || expr.Contains("'") || expr.Contains("?") || expr.Contains(":") || expr.Contains(";") || expr.Contains("&") || expr.Contains("|")) throw new MathParserException("Sono stati inseriti caratteri non ammessi");
if(expr.Contains("^")) throw new MathParserException("Impossibile effettuare l'elevamento a potenza in questa versione");
expr = expr.ToLower();
expr = expr.Replace(" ", "");
expr = expr.Replace("\r\n", "");
expr = expr.Replace("\n\r", "");
expr = expr.Replace("\n", "");
expr = expr.Replace("\r", "");
return expr;
}
#endregion

#region IDisposable Membri di
public void Dispose() {
if(myobj != null) myobj.Dispose();
}
#endregion
[+/-] Listato 6

In ordine abbiamo il metodo di sostituzione delle espressioni di sistema con le espressioni .NET. Questo non fa altro che inserire le espressioni C# al posto delle espressioni del parser prendendole dalla collezione listafunzioni. In seguito abbiamo il metodo che sostituisce le variabili in valori: è del tutto analogo al precedente salvo per il fatto che le variabili vengono racchiuse tra parentesi quadre. L'ultimo metodo privato è il metodo per la sicurezza e qui spendiamo 2 parole in più: per prima cosa nei primi 4 if determina se esistono caratteri che non devono essere inseriti (vedi sopra) poi formatta la stringa tutta in minuscolo (questo previene il possibile inserimento di codice che richiama classi del Framework, infatti iniziano tutte con lettera grande), rimpiazza gli spazi con una stringa vuota (questo permette di evitare che venga scritto codice personalizzato del tipo new classe() che diventa newclasse() quindi non compilabile) , vengono tolti tutti i tipi di ritorno a capo di modo da formare una unica espressione. Viene anche ovviato il problema di sicurezza riguardo ai punti: classe.metodo, infatti non è possibile inserire punti ma solo , che dopo il parse di sicurezza vengono convertiti in punti.

Per quanti riguarda i metodi pubblici ce ne sono 2 e sono alla base del Parse, bisogna usarli in coppia per eseguire una espressione.

#region Metodi
/// &lt;summary&gt;
/// Esegue il parse dell'espressione con l'espressione e la lista valori contenuta in memoria
/// &lt;/summary&gt;
/// &lt;exception cref="MathParserException"&gt;&lt;/exception&gt;
public void Parse() {
Parse(Expression, Valori);
}


/// &lt;summary&gt;
/// Esegue il parse dell'espressione
/// &lt;/summary&gt;
/// &lt;param name="expr"&gt;Espressione da eseguire&lt;/param&gt;
/// &lt;param name="listaValori"&gt;Lista parametri&lt;/param&gt;
/// &lt;exception cref="MathParserException"&gt;&lt;/exception&gt;
public void Parse(string expr, Dictionary&lt;string, double&gt; listaValori) {
try {
parsed = false;
listError.Clear();
valori.Clear();
if(expr == null || expr == "") throw new MathParserException("Nessuna espressione impostata");
if(listaValori != null) foreach (KeyValuePair&lt;string, double&gt; kvp in listaValori) Valori.Add(kvp.Key, kvp.Value);
expression = expr;
//Settaggio della stringa
expr = filter(expr);
expr = sobstitute(expr);
expr = sobstituteSystemFunction(expr);
expr = expr.Replace(",", ".");
//Creazione dell'assembly di esecuzione
CSharpCodeProvider cp = new CSharpCodeProvider();
ICodeCompiler ic = cp.CreateCompiler();
CompilerParameters cpar = new CompilerParameters();
cpar.GenerateInMemory = true;
cpar.GenerateExecutable = false;
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add("MathParser.dll");
string src = "using System;" +
"class myclass:MathParser.BaseClass" +
"{" +
"public myclass(){}" +
"public override double eval()" +
"{" +
"return " + expr + ";" +
"}" +
"}";
CompilerResults cr = ic.CompileAssemblyFromSource(cpar, src);
foreach (CompilerError ce in cr.Errors) {
listError.Add(ce.ErrorText);
}


if(cr.Errors.Count == 0 && cr.CompiledAssembly != null) {
Type ObjType = cr.CompiledAssembly.GetType("myclass");
try {
if(ObjType != null) myobj = (BaseClass) Activator.CreateInstance(ObjType);
} catch {
throw new MathParserException("Errore parse nell'espressione");
}
parsed = true;
} else parsed = false;
} catch (MathParserException mpe) {
parsed = false;
throw mpe;
} catch {
parsed = false;
throw new MathParserException("Errore parse nell'espressione");
}
}


/// &lt;summary&gt;
/// Valuta l'espessione inserita
/// &lt;/summary&gt;
/// &lt;returns&gt;Ritorna il valore dell'espressione inserita&lt;/returns&gt;
/// &lt;exception cref="MathParserException"&gt;&lt;/exception&gt;
public double Evaluate() {
if(Parsed)
if(myobj != null)
try {
return myobj.eval();
} catch {
throw new MathParserException("Errore nell'esecuzione dell'espressione, controllare tipi e valori");
}
else throw new MathParserException("Impossibile valutare l'espressione");
else throw new MathParserException("Impossibile valutare l'espressione se prima non si esegue il Parse dell'oggetto");
}
#endregion

[+/-] Listato 7

Il parse fa il parsing dell'espressione filtra l'espressione con i tre metodi privati esposti prima nel preciso ordine filter sobstitute e sobstituteSystemFunction in modo da evitare accavallamenti di stringe uguali del tipo se una variabile si chiama cosenza non viene confusa con la funzione cos.

Viene poi il momento della creazione dell'assembly: si crea il compilatore e con la proprietà GenerateInMemory viene detto al compilatore di creare l'assembly in memoria e non fisicamente, di modo che poi verrà eliminato:


//Creazione dell'assembly di esecuzione
CSharpCodeProvider cp = new CSharpCodeProvider();
ICodeCompiler ic = cp.CreateCompiler();
CompilerParameters cpar = new CompilerParameters();
//Comunica che l'assembly viene inserito in memoria
cpar.GenerateInMemory = true;
//Comunica che l'assembly è una dll
cpar.GenerateExecutable = false;
//Aggiunge i riferimenti alla nuova classe
cpar.ReferencedAssemblies.Add("system.dll");
cpar.ReferencedAssemblies.Add("MathParser.dll");
//Crea il codice sorgente della classe
string src = "using System;" +
"class myclass:MathParser.BaseClass" +
"{" +
"public myclass(){}" +
"public override double eval()" +
"{" +
"return " + expr + ";" +
"}" +
"}";
//Compila la classe
CompilerResults cr = ic.CompileAssemblyFromSource(cpar, src);

[+/-] Listato 8

Una volta compilato viene creata una istanza della classe myclass appena compilata e viene impostato su true il parsed di modo da comunicare al programmatore che il Parser è pronto a eseguire l'espressione. Nella lista ListError, viene inserita la lista degli errori in compilazione se ci sono stati.

//Se non ci sono stati errori crea una nuova istanza della classe
if(cr.Errors.Count == 0 && cr.CompiledAssembly != null) {
Type ObjType = cr.CompiledAssembly.GetType("myclass");
try {
if(ObjType != null) myobj = (BaseClass) Activator.CreateInstance(ObjType);
} catch {
throw new MathParserException("Errore parse nell'espressione");
}
parsed = true;
} else parsed = false;

[+/-] Listato 9

Infine il metodo Evaluate() valuta l'espressione e la restituisce chiamando il metodo eval della classe appena compilata.

Per provare il Parser in un programmino potete usare questo codice:

String c = "";
do {
c = Console.ReadLine();
MathExpressionParser p = new MathExpressionParser();
Dictionary valori = new Dictionary();
valori.Add("numero", 3.5);
valori.Add("desi", 12);
valori.Add("system", 32.254123);
try {
p.Parse(c, valori);
if(p.Parsed) Console.WriteLine("Risultato: " + p.Evaluate());
else {
foreach(String s in p.ListError) {
Console.WriteLine("Errore di no parse: " + s);
}
}
}catch(Exception ex) {
Console.WriteLine(ex.Message);
}
} while (c != "");

[+/-] Listato 10

Questo frammento codice permette di scrivere da Console l'espressione e via codice di inserire le variabili che poi andranno a sostituire i valori corretti nell'espressione dalla collezzione valori.

Espressioni del tipo sono ad esempio:

[numero]*3+4/[desy]

In questo modo si avrà un parser semplice ed efficace da usare nelle vostre applicazioni.

ALLEGATI
- Assembly MathParser
- Codice sorgente

LINKS
- Parser alfa

BUGS

I bugs finora riscontrati sono stati citati nell'intestazione.

COMMENTI
Una serie di commenti sono disponibili sul link della versione alfa del Parser

--------------------------------------------------------------------------------------------

Eccomi qua

Eccomi qua. Primo post di questo blog, e a dir la verità primo post in assoluto per quanto riguarda la programmazione. Infatti prima d'ora non ho condiviso molto del mio lavoro per gli altri (molto per problema di tempo) stavolta ho deciso di dedicare 10 minuti al blog e iniziare quest'avventura. Vediamo come va!