La famiglia di funzioni ingegneristiche in Excel sembra la sezione più semplice della guida di riferimento delle funzioni. DEC2BIN converte un numero in una stringa binaria. HEX2DEC esegue l'operazione inversa. IMSUM somma due numeri complessi. Ciascuna di esse appare come un mero esercizio di formattazione. Ma non è così. Dietro questi nomi si nascondono una codifica in complemento a due a dieci bit che la maggior parte degli sviluppatori non tocca dai tempi dell'università, un formato di numeri complessi interamente basato su stringhe, e operatori bit a bit che possono generare silenziosamente un overflow in un intero a 64 bit se si esegue lo shift prima di verificarne i limiti. Un motore di fogli di calcolo che riproduce esattamente Excel non può trascurare nessuno di questi aspetti.
Le funzioni si dividono in tre gruppi, e ciascun gruppo cela un'insidia differente. La conversione di base riguarda i numeri negativi e le soglie specifiche per ciascuna base. L'aritmetica dei numeri complessi riguarda l'analisi e la formattazione di una stringa. Le operazioni bit a bit riguardano il mantenimento dei valori entro i limiti del tipo Int64. Questo articolo esamina ciascun gruppo così come implementato in HotXLS, con le chiamate ai fogli di calcolo che scriveresti concretamente nel codice.
Conversione di base e il complemento a due a dieci bit
La conversione diretta è l'aspetto che chiunque si aspetta. DEC2BIN(9) restituisce "1001", e un secondo argomento opzionale allinea il risultato a sinistra inserendo zeri per raggiungere una larghezza fissa. L'insidia riguarda gli input negativi. Excel non scrive il segno meno. Codifica il valore come una stringa in complemento a due a dieci cifre nella base di destinazione, motivo per cui DEC2BIN(-5,10) restituisce "1111111011" anziché un formato dotato di segno. L'argomento relativo al numero di cifre viene ignorato quando il valore è negativo, poiché la codifica è vincolata rigidamente a dieci cifre.
Dieci cifre rappresentano un limite fisso, che definisce l'intervallo di valori rappresentabili per ciascuna base. In binario, il valore che determina il passaggio alla metà negativa è 512, e il modulo di avvolgimento è 1024, perciò una stringa binaria ha segno solo quando è lunga esattamente dieci caratteri e il suo valore è almeno 512. Lo stesso principio si applica alle altre basi. La base ottale utilizza una soglia intermedia pari a 2^29 e un modulo completo pari a 2^30. La base esadecimale utilizza 2^39 e 2^40. Il lettore HotXLS applica esattamente questa regola: accumula le cifre e, solo quando la stringa è larga dieci caratteri e il valore accumulato è pari o superiore alla soglia intermedia, sottrae il modulo completo per recuperare il valore con segno. Una stringa di nove caratteri è sempre non negativa, indipendentemente dalla sua grandezza.
Il codificatore opera in modo speculare. Un valore non negativo viene convertito cifra per cifra e opzionalmente completato con zeri fino alla larghezza richiesta, e viene rifiutato se supera il limite positivo della base o se la larghezza richiesta è insufficiente a contenerlo. Un valore negativo viene prima ricondotto all'intervallo aggiungendo il modulo completo, operazione che lo trasforma in un valore la cui rappresentazione nella base è sempre di dieci cifre, e successivamente tali cifre vengono emesse inserendo zeri a sinistra per raggiungere la larghezza. Il singolo controllo di intervallo condiviso, con i limiti inferiore e superiore simmetrici per ciascuna base, è ciò che garantisce la coerenza tra DEC2BIN, DEC2OCT e DEC2HEX nei casi limite.
Restano le conversioni tra basi diverse, come HEX2BIN e OCT2HEX, che cambiano base senza passare esplicitamente per il sistema decimale nel nome della funzione. L'implementazione non prevede una routine separata per ogni coppia. Esegue invece l'analisi della stringa di input in un valore decimale con segno utilizzando la base sorgente, per poi formattare tale valore decimale nella base di destinazione. Il sistema decimale funge da perno. Una singola routine di analisi e una di formattazione, combinate, coprono ogni combinazione e, poiché entrambe le parti condividono la medesima convenzione con segno a dieci cifre, un valore negativo supera la conversione mantenendo intatto il proprio segno.
I numeri complessi sono stringhe, perciò l'attività principale è il parsing
Excel non dispone di un tipo di dati complesso. Un valore complesso è rappresentato dalla stringa "a+bi", e ogni funzione della famiglia IM accetta tali stringhe restituendone una a sua volta. COMPLEX crea la stringa a partire da una parte reale e una immaginaria. Le funzioni IMSUM, IMSUB, IMPRODUCT e IMDIV analizzano i relativi argomenti, eseguono i calcoli sulle parti numeriche e formattano il risultato nuovamente in una stringa. I calcoli numerici corrispondono all'algebra di base. La difficoltà risiede interamente nel convertire il testo in due numeri a virgola mobile in modo affidabile, compito in cui si distingue il parser interno.
Due dettagli in questo parser sono facili da sbagliare. Il primo è l'unità immaginaria isolata. La stringa "i" significa una volta i, non zero e non un errore, perciò quando il coefficiente davanti al suffisso è vuoto o corrisponde a un segno più isolato, il parser deve leggerlo come valore 1, e un segno meno isolato come -1. Senza questa gestione, IMSUM("i","i") cessa di restituire 2i. Il secondo dettaglio riguarda la notazione scientifica che collide con il segno di separazione tra parte reale e immaginaria. Il parser individua tale separatore cercando un segno più o meno, ma un numero scritto come "1.5E-3" contiene un segno meno che appartiene all'esponente. Pertanto, la scansione evita di trattare un segno più o meno come separatore quando il carattere immediatamente precedente è e o E. Senza questo controllo, la parte reale verrebbe spezzata a metà in corrispondenza del segno dell'esponente e l'analisi fallirebbe su un input del tutto valido.
Il suffisso viene conservato anziché normalizzato. Excel accetta sia i sia j, e HotXLS tiene traccia della lettera utilizzata nell'input in modo che il risultato formattato presenti lo stesso suffisso. La formattazione applica quindi le abbreviazioni convenzionali: una parte immaginaria pari a uno stampa solo il suffisso, meno uno come -i, una parte immaginaria pari a zero si riduce alla sola parte reale, e una parte reale pari a zero omette il prefisso 0+.
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Engineering');
// Negative input: a ten-bit two's complement, places argument ignored.
Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
// Complex multiply on two "a+bi" strings.
Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
finally
Book.Free;
end;
end;
Le funzioni complesse trascendenti, tra cui IMSQRT, IMEXP, IMLN e IMPOWER, non operano in coordinate rettangolari. Convertono il valore analizzato in forma polare, applicano l'operazione su modulo e argomento, e lo riconvertono. Una radice quadrata dimezza l'argomento e calcola la radice del modulo. Una potenza moltiplica l'argomento ed eleva il modulo. Procedere diversamente richiederebbe di ricavare ciascuna identità in forma rettangolare, il che comporterebbe più codice e una minore stabilità numerica in prossimità dei punti di diramazione (branch cuts).
Operatori bit a bit e l'overflow da verificare preventivamente
Excel 2013 ha introdotto BITAND, BITOR, BITXOR, BITLSHIFT e BITRSHIFT. Gli operandi presentano dei vincoli: ciascuno deve essere un intero non negativo non superiore a 2^48 meno 1, e qualsiasi argomento frazionario o negativo costituisce un errore numerico. Tale limite è sufficiente a coprire qualsiasi set di flag reale pur rimanendo ampiamente entro l'intervallo rappresentabile esattamente da un tipo double, aspetto importante poiché Excel passa ogni argomento numerico sotto forma di valore a virgola mobile.
Le funzioni di shift portano con sé una regola di ordinamento critica. Uno shift a sinistra può produrre un valore molto più grande del relativo input e, se si esegue prima lo shift shl verificando il risultato successivamente, si è già verificato un overflow di Int64 e il test perde di significato. Il controllo deve precedere lo shift. HotXLS confronta l'operando con il limite massimo traslato a destra dell'entità dello shift e, solo se l'operando rientra nei limiti, esegue lo shift a sinistra effettivo. Uno shift superiore a 53 bit viene rifiutato a priori, e uno shift negativo inverte semplicemente la direzione, perciò BITLSHIFT con un conteggio negativo si comporta come uno shift a destra. Questo principio si estende ben oltre questa specifica funzione: quando è presente un controllo per prevenire l'overflow, questo deve essere eseguito sugli input, mai sul risultato che si intende proteggere.
// Bitwise calls evaluate the same way through Calculate.
Sheet.Cells[3, 1].Value := Sheet.Calculate('=BITAND(13,11)'); // 9
Sheet.Cells[4, 1].Value := Sheet.Calculate('=BITLSHIFT(5,2)'); // 20
Sheet.Cells[5, 1].Value := Sheet.Calculate('=BITRSHIFT(40,3)'); // 5
Funzioni future e il prefisso _xlfn per i nomi
Gli operatori bit a bit e un nutrito elenco di altre aggiunte successive al 2007 interagiscono con uno schema di denominazione slegato dai loro calcoli e legato al modo in cui Excel li memorizza. Il formato binario originale del foglio di lavoro assegnava a ciascuna funzione integrata una posizione numerica in una tabella fissa. Le funzioni introdotte dopo il congelamento di tale tabella sono prive di posizione. Per salvare una di queste funzioni in un file in modo che le versioni recenti di Excel la riconoscano, il nome viene scritto con il prefisso _xlfn., perciò BITAND viene memorizzato come _xlfn.BITAND sul disco anche se l'utente digita semplicemente BITAND.
Il problema è che tale regola non è uniforme. Alcune funzioni recenti hanno ottenuto posizioni in tabella e vengono scritte senza prefisso, mentre alcune vecchie funzioni nascoste sono scritte senza prefisso nonostante la loro età. HotXLS mantiene una whitelist esplicita dei nomi che richiedono il prefisso, lo aggiunge in fase di scrittura e lo rimuove in lettura, in modo che il testo della formula impostato e letto sia sempre il nome visibile in Excel. Se imposti =BITLSHIFT(5,2), il file conterrà _xlfn.BITLSHIFT, ma il valore restituito sarà comunque 20. Il prefisso è un inizio di memorizzazione che non dovrebbe mai emergere nelle formule utilizzate nel codice.
Integrazione in un foglio di lavoro
La superficie pubblica di tutto questo è ridotta. Si crea un oggetto TXLSXWorkbook, si aggiunge un foglio di lavoro e si scrive una formula in una cella tramite Cells[Row, Col].Formula ricalcolando il foglio, oppure si valuta un'espressione direttamente con il metodo Calculate del foglio di lavoro, che compila la formula rispetto a quel foglio e restituisce un tipo Variant. Gli esempi precedenti utilizzano Calculate poiché mostra il risultato di una singola chiamata ingegneristica senza lo stato del foglio circostante, ma le stesse funzioni vengono valutate in modo identico all'interno di formule di cella reali al ricalcolo della cartella di lavoro.
Le codifiche rappresentano l'elemento da tenere a mente, non i punti di chiamata. Una stringa binaria ha segno solo a dieci cifre e solo oltre la soglia intermedia per la propria base. Un numero complesso è testo, un coefficiente immaginario vuoto equivale a uno, e il parser ignora la e di un esponente. Uno shift a sinistra viene verificato prima dell'esecuzione. Gestendo correttamente questi quattro aspetti, la famiglia di funzioni ingegneristiche smette di essere fonte di sorprese legate al segno dei valori.
Se desideri integrare le tue funzioni matematiche nello stesso motore, le procedure per registrare un gestore e restituire valori sono trattate nel nostro articolo sull'estensione del motore di formule con funzioni personalizzate, e qualora tali formule debbano fare riferimento ad altri fogli per nome anziché per indirizzo di cella, la guida sui nomi definiti e le formule tra fogli illustra come si risolvono i riferimenti. Le funzioni ingegneristiche qui descritte sono incluse nel componente per fogli di calcolo HotXLS per Delphi e C++Builder, insieme alle API di lettura, scrittura e calcolo trattate altrove in questo blog.