Conversione manuale di un algoritmo MATLAB in virgola mobile in uno in virgola fissa
Questo esempio mostra come convertire un algoritmo in virgola mobile in uno in virgola fissa e come generare un codice C per l’algoritmo. L’esempio utilizza le seguenti best practice:
Separare l’algoritmo dal file di prova.
Preparare l’algoritmo per la strumentazione e la generazione di codice.
Gestire i tipi di dati e controllare la crescita di bit.
Separare le definizioni del tipo di dati dal codice dell’algoritmo creando una tabella di definizione dei dati.
Per un elenco completo delle best practice, vedere Manual Fixed-Point Conversion Best Practices.
Separazione dell’algoritmo dal file di prova
Scrivere una funzione MATLAB® mysum
che sommi gli elementi di un vettore.
function y = mysum(x) y = 0; for n = 1:length(x) y = y + x(n); end end
Poiché è necessario convertire solo la parte algoritmica in virgola fissa, risulta più efficiente strutturare il codice in modo che l’algoritmo, in cui si esegue l’elaborazione principale, sia separato dal file di prova.
Scrittura di uno script di prova
Creare gli input, chiamare l’algoritmo e tracciare i risultati nel file di prova.
Scrivere uno script MATLAB,
mysum_test
che verifichi il comportamento dell’algoritmo utilizzando tipi di dati doppi.n = 10; rng default x = 2*rand(n,1)-1; % Algorithm y = mysum(x); % Verify results y_expected = sum(double(x)); err = double(y) - y_expected
rng default
riporta le impostazioni del generatore di numeri casuali utilizzato dalla funzione rand al proprio valore predefinito, in modo che produca gli stessi numeri casuali come se si fosse riavviato MATLAB.Eseguire lo script di prova.
mysum_test
err = 0
I risultati ottenuti utilizzando
mysum
corrispondono a quelli ottenuti utilizzando la funzione MATLABsum
.
Per ulteriori informazioni, vedere Create a Test File.
Preparazione dell’algoritmo per la strumentazione e la generazione di codice
Aggiungere l’istruzione di compilazione %#codegen
nell’algoritmo, dopo la firma della funzione, per indicare che si intende strumentare l’algoritmo e generare un codice C per l’algoritmo stesso. Aggiungendo questa istruzione, l’analizzatore di codice MATLAB fornisce un aiuto per diagnosticare e correggere le violazioni che potrebbero causare errori durante la strumentazione e la generazione di codice.
function y = mysum(x) %#codegen y = 0; for n = 1:length(x) y = y + x(n); end end
Per questo algoritmo, l’analizzatore di codice nell’angolo in alto a destra della finestra dell’editor rimane verde, ad indicare che non è stato rilevato nessun problema.
Per ulteriori informazioni, vedere Prepare Your Algorithm for Code Acceleration or Code Generation.
Generazione di un codice C per l’algoritmo originale
Generare un codice C per l’algoritmo originale per verificare che l’algoritmo sia adatto per la generazione di codice e per visualizzare il codice C in virgola mobile. Utilizzare la funzione codegen
(MATLAB Coder) (richiede MATLAB Coder™) per generare una libreria C.
Aggiungere la seguente riga alla fine dello script per generare il codice C per
mysum
.codegen mysum -args {x} -config:lib -report
Eseguire nuovamente lo script di prova.
MATLAB Coder genera un codice C per la funzione
mysum
e fornisce un collegamento al report di generazione di codice.Fare clic sul collegamento per aprire il report di generazione di codice e visualizzare il codice C generato per
mysum
./* Function Definitions */ double mysum(const double x[10]) { double y; int n; y = 0.0; for (n = 0; n < 10; n++) { y += x[n]; } return y; }
Poiché C non consente indici in virgola mobile, il contatore di loop
n
, viene automaticamente dichiarato come un tipo intero. Non è necessario convertiren
in virgola fissa.Gli input
x
e gli outputy
sono dichiarati come doppi.
Gestione dei tipi di dati e controllo della crescita di bit
Testare l’algoritmo con i singoli per verificare la presenza di disallineamenti di tipo
Modificare il file di prova in modo che il tipo di dati
x
sia singolo.n = 10; rng default x = single(2*rand(n,1)-1); % Algorithm y = mysum(x); % Verify results y_expected = sum(double(x)); err = double(y) - y_expected codegen mysum -args {x} -config:lib -report
Eseguire nuovamente lo script di prova.
mysum_test
err = -4.4703e-08 ??? This assignment writes a 'single' value into a 'double' type. Code generation does not support changing types through assignment. Check preceding assignments or input type specifications for type mismatches.
La generazione di codice fallisce, riportando una mancata corrispondenza del tipo di dati sulla riga
y = y + x(n);
.Aprire il report per visualizzare l’errore.
La riga
y = y + x(n)
del report evidenzia in rosso lay
sul lato sinistro dell’assegnazione, ad indicare che è presente un errore. Il problema è chey
è dichiarata come doppia ma viene assegnata a un singolo.y + x(n)
è la somma di un doppio e di un singolo, che è un singolo. Posizionando il cursore sulle variabili e sulle espressioni del report è possibile visualizzare le informazioni relative ai loro tipi. Qui si può vedere che l’espressioney + x(n)
è un singolo.Per correggere la mancata corrispondenza di tipo, aggiornare l'algoritmo per utilizzare l'assegnazione con pedice per la somma degli elementi. Modificare
y = y + x(n)
iny(:) = y + x(n)
.function y = mysum(x) %#codegen y = 0; for n = 1:length(x) y(:) = y + x(n); end end
Utilizzando l'assegnazione con pedice, si impedisce anche la crescita di bit, che è il comportamento predefinito quando si aggiungono numeri in virgola fissa. Per ulteriori informazioni, vedere Crescita di bit. Prevenire la crescita di bit è importante perché si desidera mantenere i tipi in virgola fissa in tutto il codice. Per ulteriori informazioni, vedere Controllo della crescita di bit.
Rigenerare il codice C e aprire il report di generazione di codice. Nel codice C, il risultato è ora convertito in doppio per risolvere la mancata corrispondenza di tipo.
Costruzione di un Mex strumentato
Utilizzare la funzione buildInstrumentedMex
per strumentalizzare l’algoritmo e registrare i valori minimi e massimi di tutte le variabili con nome e intermedie. Utilizzare la funzione showInstrumentationResults
per proporre tipi di dati in virgola fissa basati su questi valori registrati. Successivamente, utilizzare i tipi di dati in virgola fissa proposti per testare l’algoritmo.
Aggiornare lo script di prova:
Dopo aver dichiarato
n
, aggiungerebuildInstrumentedMex mySum —args {zeros(n,1)} -histogram
.Modificare nuovamente
x
in doppio. Sostituirex = single(2*rand(n,1)-1);
conx = 2*rand(n,1)-1;
Anziché chiamare l’algoritmo originale, chiamare la funzione MEX generata. Modificare
y = mysum(x)
iny=mysum_mex(x)
.Dopo aver chiamato la funzione MEX, aggiungere
showInstrumentationResults mysum_mex -defaultDT numerictype(1,16) -proposeFL
. I flag-defaultDT numerictype(1,16) -proposeFL
indicano che si desidera proporre lunghezze frazionarie per una lunghezza della parola di 16 bit.Nel seguito, uno script di prova aggiornato.
%% Build instrumented mex n = 10; buildInstrumentedMex mysum -args {zeros(n,1)} -histogram %% Test inputs rng default x = 2*rand(n,1)-1; % Algorithm y = mysum_mex(x); % Verify results showInstrumentationResults mysum_mex ... -defaultDT numerictype(1,16) -proposeFL y_expected = sum(double(x)); err = double(y) - y_expected %% Generate C code codegen mysum -args {x} -config:lib -report
Eseguire nuovamente lo script di prova.
La funzione
showInstrumentationResults
propone tipi di dati e apre un report per visualizzare i risultati.Nel report, fare clic sulla scheda Variables.
showInstrumentationResults
propone una lunghezza della frazione di 13 pery
e di 15 perx
.
Nel report è possibile:
Visualizzare i valori minimi e massimi della simulazione di input
x
e di outputy
.Visualizzare i tipi di dati di
x
ey
.Visualizzare le informazioni relative a tutte le variabili, i risulti intermedi e le espressioni del codice.
Per visualizzare queste informazioni, posizionare il cursore sopra la variabile o l’espressione all’interno del report.
Visualizzare i dati dell’istogramma di
x
ey
per identificare qualsiasi valore al di fuori dell’intervallo o al di sotto della precisione, in base al tipo di dati attuale.Per visualizzare l’istogramma relativo a una specifica variabile, fare clic sull’icona dell’istogramma .
Separazione delle definizioni del tipo di dati dal codice dell’algoritmo
Anziché modificare manualmente l'algoritmo per esaminare il comportamento di ciascun tipo di dati, separare le definizioni del tipo di dati dall'algoritmo.
Modificare mysum
in modo che prenda un parametro di input T
, che è una struttura che definisce i tipi di dati di input e di output. Quando y
è definita per la prima volta, utilizza la funzione cast
come sintassi — cast(x,'like',y)
— per convertire x
nel tipo di dati desiderato.
function y = mysum(x,T) %#codegen y = cast(0,'like',T.y); for n = 1:length(x) y(:) = y + x(n); end end
Creazione di una tabella di definizione del tipo di dati
Scrivere una funzione mytypes
che definisca i diversi tipi di dati che si desiderano utilizzare per testare l’algoritmo. Nella tabella dei tipi di dati, includere i tipi di dati doppi, singoli e doppi ridimensionati, nonché i tipi di dati in virgola fissa proposti in precedenza. Prima di convertire l’algoritmo in virgola fissa, è buona prassi:
Testare la connessione tra la tabella di definizione del tipo di dati e l’algoritmo utilizzando i doppi.
Testare l’algoritmo con i singoli per rilevare tipi di dati non corrispondenti o altre problematiche.
Eseguire l’algoritmo utilizzando i doppi ridimensionati per verificare la presenza di overflow.
function T = mytypes(dt) switch dt case 'double' T.x = double([]); T.y = double([]); case 'single' T.x = single([]); T.y = single([]); case 'fixed' T.x = fi([],true,16,15); T.y = fi([],true,16,13); case 'scaled' T.x = fi([],true,16,15,... 'DataType','ScaledDouble'); T.y = fi([],true,16,13,... 'DataType','ScaledDouble'); end end
Per ulteriori informazioni, vedere Separate Data Type Definitions from Algorithm.
Aggiornamento dello script di prova per utilizzare la tabella dei tipi
Aggiornare lo script di prova mysum_test
per utilizzare la tabella dei tipi.
Per la prima esecuzione, verificare che la connessione tra la tabella e l’algoritmo utilizzi i doppi. Prima di dichiarare
n
, aggiungereT = mytypes('double');
Aggiornare la chiamata di
buildInstrumentedMex
per utilizzare il tipo diT.x
specificato nella tabella dei tipi di dati:buildInstrumentedMex mysum -args {zeros(n,1,'like',T.x),T} -histogram
Aggiornare la chiamata di
x
per utilizzare il tipo diT.x
specificato nella tabella dei tipi di dati:x = cast(2*rand(n,1)-1,'like',T.x);
Chiamare la funzione MEX passando da
T
:y = mysum_mex(x,T);
Chiamare
codegen
passando daT
:codegen mysum -args {x,T} -config:lib -report
Nel seguito, lo script di prova aggiornato.
%% Build instrumented mex T = mytypes('double'); n = 10; buildInstrumentedMex mysum ... -args {zeros(n,1,'like',T.x),T} -histogram %% Test inputs rng default x = cast(2*rand(n,1)-1,'like',T.x); % Algorithm y = mysum_mex(x,T); % Verify results showInstrumentationResults mysum_mex ... -defaultDT numerictype(1,16) -proposeFL y_expected = sum(double(x)); err = double(y) - y_expected %% Generate C code codegen mysum -args {x,T} -config:lib -report
Eseguire lo script di prova e fare clic sul collegamento per aprire il report di generazione di codice.
Il codice C generato è lo sesso del codice generato per l’algoritmo originale. Poiché la variabile
T
è utilizzata per specificare i tipi e, questi tipi, sono costanti al momento della generazione di codice,T
non viene utilizzata in fase di esecuzione e non appare nel codice generato.
Generazione di codice in virgola fissa
Aggiornare lo script di prova per utilizzare i tipi in virgola fissa proposti in precedenza e visualizzare il codice C generato.
Aggiornare lo script di prova per utilizzare i tipi in virgola fissa. Sostituire
T = mytypes('double');
conT = mytypes('fixed');
, quindi salvare lo script.Eseguire lo script di prova e visualizzare il codice C generato.
Questa versione del codice C non è molto efficiente poiché comporta molta gestione dell'overflow. Il passaggio successivo consiste nell’ottimizzare i tipi di dati per evitare overflow.
Ottimizzazione dei tipi di dati
Utilizzare i doppi ridimensionati per rilevare l’overflow
I doppi ridimensionati sono un ibrido tra i numeri in virgola mobile e i numeri in virgola fissa. Fixed-Point Designer™ li archivia come doppi conservando le informazioni relative al ridimensionamento, la firma e la lunghezza della parola. Poiché tutta l'aritmetica viene eseguita in doppia precisione, è possibile vedere tutti gli overflow che si verificano.
Aggiornare lo script di prova per utilizzare i doppi ridimensionati. Sostituire
T = mytypes('fixed');
conT = mytypes('scaled');
Eseguire nuovamente lo script di prova.
La prova viene eseguita utilizzando doppi ridimensionati e viene visualizzato il report. Non vengono rilevati overflow.
Finora, sono stati eseguiti script di prova utilizzando degli input casuali; è quindi improbabile che la prova abbia esplorato l’intero intervallo operativo dell’algoritmo.
Trovare l’intero intervallo di input.
range(T.x)
-1.000000000000000 0.999969482421875 DataTypeMode: Fixed-point: binary point scaling Signedness: Signed WordLength: 16 FractionLength: 15
Aggiornare lo script per testare il caso del limite negativo. Eseguire
mysum_mex
con l’input casuale originale e con un input che verifichi l’intero intervallo e aggreghi i risultati.%% Build instrumented mex T = mytypes('scaled'); n = 10; buildInstrumentedMex mysum ... -args {zeros(n,1,'like',T.x),T} -histogram %% Test inputs rng default x = cast(2*rand(n,1)-1,'like',T.x); y = mysum_mex(x,T); % Run once with this set of inputs y_expected = sum(double(x)); err = double(y) - y_expected % Run again with this set of inputs. The logs will aggregate. x = -ones(n,1,'like',T.x); y = mysum_mex(x,T); y_expected = sum(double(x)); err = double(y) - y_expected % Verify results showInstrumentationResults mysum_mex ... -defaultDT numerictype(1,16) -proposeFL y_expected = sum(double(x)); err = double(y) - y_expected %% Generate C code codegen mysum -args {x,T} -config:lib -report
Eseguire nuovamente lo script di prova.
Il test viene eseguito e
y
supera l’intervallo del tipo di dati in virgola fissa.showInstrumentationResults
propone una nuova lunghezza della frazione di 11 pery
.Aggiornare lo script di prova per utilizzare i doppi ridimensionati con il nuovo tipo proposto per
y
. InmyTypes.m
, per il caso'scaled'
,T.y = fi([],true,16,11,'DataType','ScaledDouble')
Eseguire nuovamente lo script di prova.
Non sono presenti overflow.
Generazione di codice per il tipo in virgola fissa proposto
Aggiornare la tabella dei tipi di dati per utilizzare il tipo in virgola fissa proposto e generare il codice.
In
myTypes.m
, per il caso'fixed'
,T.y = fi([],true,16,11)
Aggiornare lo script di prova
mysum_test
, per utilizzareT = mytypes('fixed');
Eseguire lo script di prova, quindi fare clic sul collegamento Visualizza Report per visualizzare il codice C generato.
short mysum(const short x[10]) { short y; int n; int i; int i1; int i2; int i3; y = 0; for (n = 0; n < 10; n++) { i = y << 4; i1 = x[n]; if ((i & 1048576) != 0) { i2 = i | -1048576; } else { i2 = i & 1048575; } if ((i1 & 1048576) != 0) { i3 = i1 | -1048576; } else { i3 = i1 & 1048575; } i = i2 + i3; if ((i & 1048576) != 0) { i |= -1048576; } else { i &= 1048575; } i = (i + 8) >> 4; if (i > 32767) { i = 32767; } else { if (i < -32768) { i = -32768; } } y = (short)i; } return y; }
Per impostazione predefinita, l’aritmetica
fi
utilizza la saturazione in overflow e l’arrotondamento al più vicino che risulta in un codice inefficiente.
Modifica delle impostazioni fimath
Per rendere più efficiente il codice generato, utilizzare le impostazioni matematiche (fimath
) in virgola fissa, che sono più appropriate per la generazione di codice C: avvolgimento sull’overflow e arrotondamento per difetto.
Aggiungere un caso
'fixed2'
inmyTypes.m
:case 'fixed2' F = fimath('RoundingMethod', 'Floor', ... 'OverflowAction', 'Wrap', ... 'ProductMode', 'FullPrecision', ... 'SumMode', 'KeepLSB', ... 'SumWordLength', 32, ... 'CastBeforeSum', true); T.x = fi([],true,16,15,F); T.y = fi([],true,16,11,F);
Suggerimento
Anziché inserire le proprietà
fimath
manualmente, è possibile utilizzare l’opzione Insert fimath del MATLAB Editor. Per ulteriori informazioni, vedere Building fimath Object Constructors in a GUI.Aggiornare lo script di prova per utilizzare
'fixed2'
, eseguire lo script, quindi visualizzare il codice C generato.short mysum(const short x[10]) { short y; int n; y = 0; for (n = 0; n < 10; n++) { y = (short)(((y << 4) + x[n]) >> 4); } return y; }
Il codice generato è più efficiente ma
y
viene spostata per allinearsi conx
, e si perdono 4 bit di precisione.Per correggere questa perdita di precisione, aggiornare la lunghezza della parola di
y
a 32 bit e mantenere 15 bit di precisione per allinearsi conx
.Aggiungere un caso
'fixed32'
inmyTypes.m
:case 'fixed32' F = fimath('RoundingMethod', 'Floor', ... 'OverflowAction', 'Wrap', ... 'ProductMode', 'FullPrecision', ... 'SumMode', 'KeepLSB', ... 'SumWordLength', 32, ... 'CastBeforeSum', true); T.x = fi([],true,16,15,F); T.y = fi([],true,32,15,F);
Aggiornare lo script di prova per utilizzare
'fixed32'
e eseguire lo script per generare nuovamente il codice.Ora, il codice generato è estremamente efficiente.
int mysum(const short x[10]) { int y; int n; y = 0; for (n = 0; n < 10; n++) { y += x[n]; } return y; }
Per ulteriori informazioni, vedere Optimize Your Algorithm.