2016-01-28 31 views
6

sto facendo un progetto in cui faccio RGB alle conversioni luma, e ho alcuni problemi di arrotondamento con la bandiera -mno-SSE2:gcc -mno-SSE2 arrotondamento

Ecco il codice di prova:

#include <stdio.h> 
#include <stdint.h> 

static double rec709_luma_coeff[3] = {0.2126, 0.7152, 0.0722}; 

int main() 
{ 
    uint16_t n = 242 * rec709_luma_coeff[0] + 242 * rec709_luma_coeff[1] + 242 * rec709_luma_coeff[2]; 

    printf("%u\n", n); 
    return 0; 
} 

ed ecco cosa ottengo:

[email protected]>gcc -mno-sse2 test.c -o test && ./test 
241 
[email protected]> gcc test.c -o test && ./test 
242 

suppongo che gcc utilizza ottimizzazioni SSE2 per double moltiplicazioni, ma quello che non capisco è il motivo per cui la versione ottimizzata sarebbe quella corretta.

Inoltre, cosa consiglia di utilizzare per ottenere risultati più coerenti, ceil() o floor()?

+5

Non ha nulla a che fare con l'ottimizzazione. Nessun SSE2 significa uso della vecchia FPU x87, che è ** più larga ** di SSE2. In un certo senso, i risultati x87 vengono eseguiti con maggiore precisione, ma i risultati potrebbero essere diversi da quelli eseguiti utilizzando SSE2 –

+0

Stranamente, quando si compila con il flag '-O2', il problema scompare ... – perror

+0

Se si attiva l'ottimizzazione, si ottiene anche 242 con '-mno-sse2' – jofel

risposta

0

TL: DR utilizzare lrint(x) o (int)rint(x) per convertire da float in int con round-to-closer anziché in troncamento. Sfortunatamente non tutti i compilatori incorporano in modo efficiente le stesse funzioni matematiche. Vedere round() for float in C++


gcc -mno-sse2 deve usare x87 per double, anche in codice a 64 bit. i registri x87 hanno una precisione interna di 80 bit, ma SSE2 utilizza il formato IEEE binary64 (aka double) in modo nativo nei registri XMM, quindi tutti i provini sono arrotondati a 64-bit double ad ogni passaggio.

Il problema non è nulla di interessante come the double rounding problem (80 bit -> 64 bit, quindi in intero). Non è nemmeno da gcc -O0 (l'impostazione predefinita: nessuna ottimizzazione aggiuntiva) di arrotondamento quando si memorizzano i provvisori in memoria, perché l'intera operazione è stata eseguita in una istruzione C in modo da utilizzare solo i registri x87 per l'intera espressione.


È semplicemente che precisione di 80 bit porta ad un risultato che appena sotto 242,0 e viene troncato a 241 da C di flottante> int semantica, mentre SSE2 produce un risultato appena sopra 242,0 che tronca a 242. Per x87, l'arrotondamento al numero intero successivo più basso avviene in modo coerente, non solo 242, per qualsiasi input compreso tra 1 e 65535. (Ho creato una versione del programma utilizzando atoi(argv[1]) in modo da poter verificare altri valori e con -O3).

Ricordare che int foo = 123.99999 è 123, perché C utilizza la modalità di arrotondamento "troncatura" (verso zero). Per i numeri non negativi, questo è lo stesso di floor (che arrotonda verso -Infinity). https://en.wikipedia.org/wiki/Floating-point_arithmetic#Rounding_modes.


double non può rappresentare i coefficienti esattamente: li ho stampato con gdb ed ho ottenuto: {0.21260000000000001, 0.71519999999999995, 0.0722}. Queste rappresentazioni decimali non sono probabilmente rappresentazioni esatte dei valori in virgola mobile di base-2. Ma sono abbastanza vicini da vedere che i coefficienti si sommano a 0.99999999999999996 (usando una calcolatrice di precisione arbitraria).

Veniamo consistono arrotondando perché x87 precisione interna è superiore alla precisione dei coefficienti, quindi la somma errori di arrotondamento in n * rec709_luma_coeff[0] e così via, e sommando i risultati, è ~ 2^11 inferiore alla differenza tra la somma dei coefficienti e 1.0. (Significato a 64 bit e 53 bit).

La vera domanda è come la versione SSE2 è riuscita a funzionare! Presumibilmente intorno al più vicino - anche sui temporari accade di andare in su in casi sufficienti, almeno per 242. Capita di produrre l'input originale per più casi di no, ma produce input-1 per 5, 7, 10, 13, 14, 20 ... (252 dei primi 1000 numeri da 1..1000 sono "munged" dalla versione SSE2, quindi non è come funziona sempre o.)


Con -O3 per la sorgente, esegue il calcolo in fase di compilazione con precisione estesa e produce il risultato esatto. cioè, compila lo stesso di printf("%u\n", n);.


E BTW si dovrebbe usare staticconst per voi costanti in modo da gcc può ottimizzare al meglio. static è molto meglio del semplice global, però, perché il compilatore può vedere che nulla nell'unità di compilazione scrive i valori o passa il loro indirizzo ovunque, quindi può trattarli come se fossero const.