Articolo tecnico

Funzioni Ingegneristiche in Delphi: Conversione di Base, Matematica Complessa

La famiglia ingegneristica in Excel si legge come l'angolo più semplice del riferimento delle funzioni. DEC2BIN trasforma un numero in una stringa binaria. HEX2DEC lo riporta indietro. IMSUM somma due numeri complessi. Ciascuna di queste sembra un esercizio di formattazione. Non lo sono. Dietro a questi nomi si nasconde una codifica in complemento a due a dieci bit che la maggior parte degli sviluppatori non tocca dai tempi di un corso di architettura dei calcolatori, un formato per numeri complessi che vive interamente all'interno delle stringhe, e operatori bit a bit (bitwise) che manderanno silenziosamente in overflow un intero a 64 bit se esegui lo shift prima di controllare. Un motore per fogli di calcolo che riproduce esattamente Excel non può arrotondare nulla di tutto ciò

Le funzioni si dividono in tre gruppi, e ogni gruppo nasconde una trappola diversa. La conversione di base riguarda i numeri negativi e le soglie per ogni base. L'aritmetica complessa riguarda il parsing e la formattazione di una stringa. Le operazioni bit a bit riguardano il rimanere entro i limiti di un Int64. Questo articolo analizza ciascun gruppo così come li implementa HotXLS, con le chiamate per i fogli di lavoro che scriveresti effettivamente

Conversione di base e complemento a due a dieci bit

La direzione in avanti è la parte che tutti si aspettano. DEC2BIN(9) restituisce "1001" e un secondo argomento facoltativo riempie il risultato a sinistra (left-pad) fino a una larghezza fissa. La trappola è l'input negativo. Excel non scrive un segno meno. Codifica il valore come una stringa in complemento a due di dieci cifre nella base di destinazione, motivo per cui DEC2BIN(-5,10) restituisce "1111111011" invece di qualsiasi cosa con un segno. L'argomento posizioni (places) viene ignorato quando il valore è negativo, perché la codifica è già fissata a dieci cifre

Dieci cifre sono un budget fisso, e quel budget stabilisce l'intervallo rappresentabile per base. In binario la grandezza che passa nella metà negativa è 512, e il modulo di avvolgimento (wrap) è 1024, quindi una stringa binaria è con segno solo quando è lunga esattamente dieci caratteri e il suo valore è almeno 512. La stessa idea si scala con la base. L'ottale utilizza una mezza soglia di 2^29 e un modulo completo di 2^30. L'esadecimale utilizza 2^39 e 2^40. Il lettore di HotXLS applica esattamente questa regola: accumula le cifre, e solo quando la stringa è larga dieci caratteri e il valore accumulato si trova in corrispondenza o al di sopra della mezza soglia sottrae il modulo completo per recuperare il valore con segno. Una stringa di nove caratteri è sempre non negativa, indipendentemente dalla sua grandezza

Il codificatore è l'immagine speculare. Un valore non negativo viene convertito cifra per cifra e opzionalmente riempito con zeri alla larghezza richiesta, e viene respinto se supera il tetto massimo positivo della base o se la larghezza richiesta è troppo stretta per contenerlo. Un valore negativo viene prima portato nell'intervallo aggiungendo il modulo completo, il che lo trasforma in un valore la cui rappresentazione di base è sempre di dieci cifre, e poi le cifre vengono emesse con zeri iniziali per riempire la larghezza. L'unico controllo condiviso dell'intervallo, con limiti inferiori e superiori simmetrici per base, è ciò che mantiene DEC2BIN, DEC2OCT e DEC2HEX coerenti tra loro ai loro estremi

Ciò che resta sono le conversioni tra basi, quelle come HEX2BIN e OCT2HEX che cambiano base senza passare attraverso il decimale nel nome della funzione. L'implementazione non comporta una routine separata per ogni coppia ordinata. Esegue il parsing della stringa di input in un valore decimale con segno utilizzando la base di partenza, quindi formatta quel valore decimale nella base di destinazione. Il decimale è il perno. Una routine di parsing e una di formattazione, composte, coprono ogni combinazione, e poiché entrambe le metà condividono la stessa convenzione con segno a dieci cifre, un valore negativo sopravvive al viaggio con il suo segno intatto

I numeri complessi sono stringhe, quindi il lavoro è il parsing

Excel non ha un tipo di dati per i numeri complessi. Un valore complesso è la stringa "a+bi", e ogni funzione nella famiglia IM accetta queste stringhe e ne restituisce una. COMPLEX costruisce la stringa da una parte reale e una parte immaginaria. IMSUM, IMSUB, IMPRODUCT e IMDIV analizzano i loro argomenti, eseguono l'aritmetica sulle parti numeriche e formattano nuovamente il risultato in una stringa. Il lavoro numerico è semplice algebra. La difficoltà sta interamente nel trasformare il testo in due numeri in virgola mobile in modo affidabile, ed è qui che il parser interno si guadagna da vivere

Due dettagli in quel parser sono facili da sbagliare. Il primo è la semplice unità immaginaria. La stringa "i" significa uno moltiplicato per i, non zero e non un errore, quindi quando il coefficiente davanti al suffisso è vuoto o è un segno più solitario, il parser deve leggerlo come il valore 1 e un segno meno solitario come -1. Salta questo passaggio e IMSUM("i","i") smette di essere 2i. Il secondo è la notazione scientifica che si scontra con il segno che separa le parti reali e immaginarie. Il parser trova quel separatore scansionando in cerca di un più o un meno, ma un numero scritto come "1.5E-3" contiene un meno che appartiene all'esponente. La scansione, pertanto, si rifiuta di trattare un più o un meno come separatore quando il carattere che lo precede immediatamente è e o E. Senza quella salvaguardia, la parte reale verrebbe spezzata a metà al segno dell'esponente e il parsing fallirebbe su un input perfettamente valido

Il suffisso stesso viene conservato anziché normalizzato. Excel accetta sia i sia j, e HotXLS ricorda quale ha utilizzato l'input in modo che il risultato formattato mantenga la stessa lettera. La formattazione applica quindi le scorciatoie convenzionali: una parte immaginaria di uno viene stampata solo come suffisso, un meno uno come -i, una parte immaginaria a zero collassa in un semplice numero reale, e una parte reale a zero perde il 0+ iniziale

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Engineering');
    // Input negativo: un complemento a due a dieci bit, l'argomento posizioni è ignorato.
    Sheet.Cells[1, 1].Value := Sheet.Calculate('=DEC2BIN(-5,10)'); // 1111111011
    // Moltiplicazione complessa su due stringhe "a+bi".
    Sheet.Cells[2, 1].Value := Sheet.Calculate('=IMPRODUCT("3+4i","1+2i")'); // -5+10i
  finally
    Book.Free;
  end;
end;

Le funzioni complesse trascendenti, IMSQRT, IMEXP, IMLN e IMPOWER tra esse, non lavorano in coordinate rettangolari. Convertono il valore parsato in forma polare, applicano l'operazione sul modulo e sull'argomento, e tornano indietro. Una radice quadrata dimezza l'argomento e calcola la radice del modulo. Una potenza moltiplica l'argomento ed eleva il modulo. Farlo in qualsiasi altro modo significherebbe ricavare nuovamente ciascuna identità in forma rettangolare, il che comporta sia più codice sia una minore stabilità numerica vicino ai tagli di diramazione (branch cuts)

Operatori bit a bit e l'overflow che devi controllare per primo

Excel 2013 ha aggiunto BITAND, BITOR, BITXOR, BITLSHIFT e BITRSHIFT. Gli operandi sono vincolati: ciascuno deve essere un intero non negativo non più grande di 2^48 meno 1, e qualsiasi argomento frazionario o negativo è un errore numerico. Tale limite è abbastanza generoso da coprire qualsiasi insieme di flag realistico rimanendo ampiamente entro l'intervallo rappresentabile in modo esatto di un valore di tipo double, il che è importante perché Excel trasmette ogni argomento numerico come un valore in virgola mobile

Le funzioni di spostamento (shift) comportano un'unica regola di ordinamento che causa realmente problemi. Uno shift a sinistra può produrre un valore molto più grande del suo input, e se esegui lo shl prima e ispezioni il risultato dopo hai già mandato in overflow l'Int64 e il test è privo di senso. Il controllo deve avvenire prima dello spostamento. HotXLS confronta l'operando con il limite massimo (ceiling) spostato a destra in base alla quantità di shift, e solo se l'operando rientra nei limiti esegue l'effettivo shift a sinistra. Un'ampiezza di shift oltre i 53 bit viene respinta a priori, e uno shift negativo semplicemente inverte la direzione, quindi BITLSHIFT con un conteggio negativo si comporta come uno shift a destra. Il principio si generalizza ben oltre questa singola funzione: quando esiste una salvaguardia per prevenire un overflow, questa deve essere eseguita sugli input, mai sul risultato che era destinata a proteggere

// Le chiamate bit a bit si valutano allo stesso modo tramite 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 del nome _xlfn

Gli operatori bit a bit e un lungo elenco di altre aggiunte post-2007 interagiscono con uno schema di denominazione che non ha nulla a che fare con ciò che calcolano e tutto a che fare con il modo in cui Excel li archivia. Il formato binario originale del foglio di lavoro assegnava a ciascuna funzione integrata uno slot numerico in una tabella fissa. Le funzioni inventate dopo il congelamento di quella tabella non hanno alcuno slot. Per salvare una funzione del genere in un file e fare in modo che un Excel moderno la riconosca, il nome è scritto con un prefisso _xlfn., quindi BITAND è salvata come _xlfn.BITAND su disco anche se l'utente digita solo e sempre BITAND

L'intoppo è che la regola non è uniforme. Ad alcune funzioni più recenti sono stati assegnati slot di tabella e vengono scritte in modo nudo e crudo, mentre alcune funzioni legacy nascoste vengono anch'esse scritte senza prefisso, nonostante la loro età. HotXLS mantiene una whitelist esplicita dei nomi che necessitano del prefisso, lo aggiunge in fase di scrittura e lo rimuove in fase di lettura, quindi il testo della formula che imposti e rileggi è sempre il nome pulito presentato ad Excel. Imposti =BITLSHIFT(5,2), il file contiene _xlfn.BITLSHIFT, e il valore restituito è 20 indipendentemente da ciò. Il prefisso è un dettaglio di archiviazione che non dovrebbe mai trapelare nelle formule con cui lavori nel codice

Mettendo tutto insieme in un foglio di lavoro

La superficie pubblica per tutto questo è ridotta. Crea un TXLSXWorkbook, aggiungi un foglio di lavoro, e scrivi una formula in una cella attraverso Cells[Row, Col].Formula ricalcolando poi, oppure valuta un'espressione direttamente con il metodo Calculate del foglio di lavoro, che compila la formula rispetto a quel foglio e restituisce un Variant. Gli esempi sopra usano Calculate perché mostrano il risultato di una singola chiamata ingegneristica senza lo stato del foglio circostante, ma le stesse funzioni si valutano in modo identico all'interno delle formule reali delle celle quando la cartella di lavoro si ricalcola

Le codifiche sono la parte da tenere a mente, non i punti di chiamata. Una stringa binaria è con segno solo a dieci cifre e solo oltre la mezza soglia per la sua base. Un numero complesso è testo, un coefficiente immaginario vuoto vale uno, e il parser scavalca la e di un esponente. Uno shift a sinistra viene verificato prima che effettui lo spostamento. Metti in riga questi quattro fatti e la famiglia ingegneristica smetterà di essere una fonte di sorprese con segni sballati (off-by-a-sign)

Se stai cablando la tua personale matematica di dominio nello stesso motore, le meccaniche per la registrazione di un gestore (handler) e la restituzione dei valori sono trattate nel nostro articolo sull'estensione del motore delle formule con funzioni personalizzate, e quando tali formule devono attraversare i fogli per nome anziché per indirizzo di cella, la panoramica sui nomi definiti e sulle formule incrociate mostra come si risolvono i riferimenti. Le funzioni ingegneristiche qui descritte vengono fornite come parte del componente per fogli di calcolo HotXLS per Delphi e C++Builder, insieme alle API di lettura, scrittura e calcolo trattate altrove in questo blog