2017-02-09 1 views
0

J'essaie d'améliorer les performances surf.cpp. A partir de la ligne 140, vous pouvez trouver cette fonction:Pourquoi utiliser double, puis lancer pour flotter?

inline float calcHaarPattern(const int* origin, const SurfHF* f, int n) 
{ 
    double d = 0; 
    for(int k = 0; k < n; k++) 
     d += (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w; 
    return (float)d; 
} 

Exécution d'une analyse Vectorisation Intel Advisor, il montre que « 1 conversions de types de données actuelles » qui pourrait être inefficace (surtout dans vectorisation).

Mais ma question est: en regardant cette fonction, pourquoi les auteurs auraient créé d comme double, puis l'ont casté en float? S'ils voulaient un nombre décimal, float serait ok. La seule raison qui me vient à l'esprit est que puisque double est plus précis que float, alors il peut représenter des nombres plus petits, mais la valeur finale est assez grande pour être stockée dans un float, mais je n'ai effectué aucun test sur la valeur d .

Une autre raison possible?

+1

Peut-être 'f [k] .w' est un' double'. –

+0

@ tobi303 ehm [nope] (http://stackoverflow.com/questions/10108053/ranges-of-floating-point-datatype-in-c) – justHelloWorld

+0

@ FrançoisAndrieux alors quoi? :) Vous pouvez additionner deux doubles et enregistrer le résultat dans un flotteur sans aucun casting, non? – justHelloWorld

Répondre

7

Parce que l'auteur veut avoir une précision plus élevée pendant le calcul, alors seulement autour du résultat final. Cela revient à conserver des chiffres plus significatifs lors du calcul.

Plus précisément, lors de l'addition et de la soustraction, une erreur peut être accumulée. Cette erreur peut être considérable lorsqu'un grand nombre de nombres à virgule flottante est impliqué.

+0

C'est bizarre. Pourquoi ne lancent-ils pas 'f [k] .w' en' double' ** avant de se multiplier avec l'entier? De cette façon, le code pourrait tirer parti de la plus grande précision de la somme, mais décide de ne pas le faire par rapport aux sommaires. C'est vraiment étrange. – IInspectable

+0

Il semble que seule l'accumulation de 1 à n est promue à doubler. À l'intérieur de la boucle, ces 4 chiffres sont conservés en résolution inférieure ... –

+0

@IInspectable peut-être parce que cela ne fait pas beaucoup de différence? Voir l'exemple dans ma réponse. –

4

Vous avez remis en question la réponse en disant qu'il faut utiliser une plus grande précision pendant la sommation, mais je ne vois pas pourquoi. Cette réponse est correcte. Considérez cette version simplifiée avec des chiffres complètement confectionnés:

#include <iostream> 
#include <iomanip> 

float w = 0.; 

float calcFloat(const int* origin, int n) 
{ 
    float d = 0; 
    for(int k = 0; k < n; k++) 
     d += origin[k] * w; 
    return (float)d; 
} 

float calcDouble(const int* origin, int n) 
{ 
    double d = 0; 
    for(int k = 0; k < n; k++) 
     d += origin[k] * w; 
    return (float)d; 
} 


int main() 
{ 
    int o[] = { 1111, 22222, 33333, 444444, 5555 }; 
    std::cout << std::setprecision(9) << calcFloat(o, 5) << '\n'; 
    std::cout << std::setprecision(9) << calcDouble(o, 5) << '\n'; 
} 

Les résultats sont les suivants:

6254.77979 
6254.7793 

Ainsi, même si les entrées sont les mêmes dans les deux cas, vous obtenez un résultat différent en utilisant double pour la sommation intermédiaire. Changer calcDouble pour utiliser (double)wne change pas la sortie.

Ceci suggère que le calcul de (origin[f[k].p0] + origin[f[k].p3] - origin[f[k].p1] - origin[f[k].p2])*f[k].w est assez précis, mais l'accumulation d'erreurs pendant l'addition est ce qu'ils essaient d'éviter.

Cela est dû à la façon dont les erreurs sont propagées lorsque vous travaillez avec des nombres à virgule flottante. Citant The Floating-Point Guide: Error Propagation:

En général:

  • La multiplication et la division sont des opérations « sûres »
  • addition et la soustraction sont dangereuses, parce que lorsque le nombre de grandeurs différentes sont impliquées, les chiffres de la plus petite amplitude nombre sont perdus.

Donc, vous voulez le type supérieur de précision pour la somme, ce qui implique plus. Multiplier l'entier par un double au lieu d'un float n'importe pas autant: vous obtiendrez quelque chose qui est à peu près aussi précis que la valeur float par laquelle vous commencez (tant que le résultat n'est pas très très grand ou très très petit).Mais additionner float valeurs qui pourraient avoir des ordres de grandeur très différents, même lorsque les nombres individuels eux-mêmes sont représentables comme float, va accumuler des erreurs et s'écarter de plus en plus de la vraie réponse.

Pour voir que dans l'action:

float f1 = 1e4, f2 = 1e-4; 
std::cout << (f1 + f2) << '\n'; 
std::cout << (double(f1) + f2) << '\n'; 

ou équivalent, mais plus proche du code d'origine:

float f1 = 1e4, f2 = 1e-4; 
float f = f1; 
f += f2; 
double d = f1; 
d += f2; 
std::cout << f << '\n'; 
std::cout << d << '\n'; 

Le résultat est:

10000                                                    
10000.0001 

Ajout des deux flotteurs perd précision. Ajouter le flotteur à un double donne la bonne réponse, même si les entrées étaient identiques. Vous avez besoin de neuf chiffres significatifs pour représenter la valeur correcte, et c'est trop pour un float.

+0

* "Changer' calcDouble' pour utiliser '(double) w' ne change pas la sortie." * - Pour être juste, il ne change pas la sortie, étant donné l'entrée ** que vous avez choisi au hasard **. C'est loin d'être une preuve, je suis désolé. – IInspectable

+1

Je suis désolé, mais vous n'avez pas demandé de preuve et je n'ai pas prétendu en donner un. Si vous ne pouvez pas comprendre pourquoi utiliser 'double 'pour les questions de somme, vous devez lire les nombres à virgule flottante et la propagation des erreurs. J'ai ajouté une référence pour vous faire cela. –

+0

@Jonathan Wakely Grand exemple et explication. –