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 static
const
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
.
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 –
Stranamente, quando si compila con il flag '-O2', il problema scompare ... – perror
Se si attiva l'ottimizzazione, si ottiene anche 242 con '-mno-sse2' – jofel