2017-04-10 6 views
1

Aujourd'hui, dans mes cours de programmation en C++, mon proff m'indiquait qu'il ne fallait jamais comparer directement deux valeurs à virgule flottante. J'ai donc essayé ce morceau de code et j'ai trouvé la raison de sa déclaration.Arithmétique à virgule flottante

double l_Value=94.9; 
print("%.20lf",l_Value); 

Et j'ai trouvé les résultats que 94,89999999 (une erreur relative)

Je comprends que les nombres flottants ne sont pas stockés dans la façon dont on présente au code. Presser ces uns et les zéros sous forme binaire implique quelques erreurs d'arrondis.

Je cherche des solutions à deux problèmes. 1. Façon efficace de comparer deux valeurs flottantes. 2. Comment ajouter une valeur flottante à une autre. Exemple. Ajouter 0.1111 à 94.4345 pour obtenir la valeur exacte comme 94.5456

Merci d'avance.

+2

Lire [cet article SO] (http://stackoverflow.com/questions/588004/is-floating-point-math-broken). –

+0

"on ne devrait jamais comparer deux valeurs à virgule flottante directement." Si votre professeur a dit cela, prenez un nouveau professeur. Ce qu'il aurait dû dire, c'est de ne jamais vérifier deux valeurs à virgule flottante pour ** l'égalité ** directement. Il n'y a aucun problème à les comparer pour voir si l'un est supérieur ou inférieur à l'autre. – JeremyP

+0

Ah j'ai oublié, il a dit que le contrôle de l'égalité ne devrait pas être fait. – AstroMax

Répondre

1
  1. moyen efficace pour comparer les deux valeurs flottantes.

Un double a,b; if (a == b) simple est un moyen efficace de comparer deux valeurs flottantes. Pourtant, comme OP l'a remarqué, cela peut ne pas atteindre l'objectif global de codage. De meilleurs moyens dépendent du contexte de la comparaison, quelque chose non fourni par OP. Voir loin ci-dessous.

  1. Comment ajouter une valeur flottante à un autre. Exemple. Ajouter 0,1111 à 94,4345 pour obtenir la valeur exacte 94,5456

Les valeurs flottantes code source ont gamme illimitée efficace et une précision telle que 1.23456789e1234567. La conversion de ce texte en double est généralement limitée à l'une des deux valeurs suivantes: . Le plus proche est sélectionné, mais cela peut ne pas correspondre exactement.

Ni 0.1111, 94.4345, 94.5456 peut être representably exactement comme double typique.

OP a des choix:

1.) Utilisez un autre type autre que double, float. Différentes bibliothèques offrent des types à virgule flottante décimale.

2) Limiter le code aux plates-formes rares qui prennent en charge double à une forme de base 10 telle que FLT_RADIX == 10.

3) Écrivez votre propre code pour gérer les entrées utilisateur comme "0.1111" dans une structure/chaîne et effectuez les opérations nécessaires.

4) Traiter l'entrée utilisateur comme les chaînes et la convertir en type entier, toujours avec les routines supportées pour lire/calculer/et écrire.

5) Accepter que les opérations à virgule flottante ne sont pas mathématiquement exactes et gèrent l'erreur d'arrondi.

double a = 0.1111; 
printf("a: %.*e\n", DBL_DECIMAL_DIG -1 , a); 
double b = 94.4345; 
printf("b: %.*e\n", DBL_DECIMAL_DIG -1 , b); 
double sum = a + b; 
printf("sum: %.*e\n", DBL_DECIMAL_DIG -1 , sum); 
printf("%.4f\n", sum); 

Sortie

a: 1.1110000000000000e-01 
b: 9.4434500000000000e+01 
sum: 9.4545599999999993e+01 
94.5456 // Desired textual output based on a rounded `sum` to the nearest 0.0001 

Plus sur # 1

Si une exacte pour comparer n'est pas demandée, mais une sorte de « sont les deux valeurs assez proches ? ", une définition de" assez proche "est nécessaire - dont ils sont nombreux.

La section "assez proche" compare la distance en examinant le ULP des deux nombres. C'est une différence linéaire quand les valeurs sont dans la même puissance de deux et devient logarithmique autrement. Bien sûr, le changement de signe est un problème.

float exemple:
Tenez compte de tous float fini de l'ordre le plus négatif à la plus positive. Le code suivant, un peu portable, renvoie un entier pour chaque float avec même commande.

uint32_t sequence_f(float x) { 
    union { 
    float f; 
    uint32_t u32; 
    } u; 
    assert(sizeof(float) == sizeof(uint32_t)); 
    u.f = x; 
    if (u.u32 & 0x80000000) { 
    u.u32 ^= 0x80000000; 
    return 0x80000000 - u.u32; 
    } 
    return u.u3 
} 

Maintenant, pour déterminer si deux float sont "suffisamment proche", simple comparer deux entiers.

static bool close_enough(float x, float y, uint32_t ULP_delta) { 
    uint32_t ullx = sequence_f(x); 
    uint32_t ully = sequence_f(y); 
    if (ullx > ully) return (ullx - ully) <= ULP_delta; 
    return (ully - ullx) <= ULP_delta; 
} 
0

La façon dont j'ai généralement fait ceci est d'avoir une fonction de comparaison d'égalité personnalisée. L'idée de base, est que vous avez une certaine tolérance, disons 0,0001 ou quelque chose. Ensuite, vous soustrayez vos deux nombres et prenez leur valeur absolue, et si elle est inférieure à votre tolérance, vous la traitez comme égale. Il y a d'autres stratégies qui peuvent être plus appropriées dans certaines situations, bien sûr.

+1

Le problème avec la vérification par rapport à un seuil est que le choix de l'epsilon est toujours arbitraire. Demandez-vous si vous avez une bonne raison pour laquelle vous devez d'abord vérifier l'égalité. – nucleon

0
  1. Define pour vous-même un niveau de tolérance e (par exemple, e=.0001) et vérifier si abs(a-b) <= e

  2. Tu ne vas pas pour obtenir une valeur « exacte » avec virgule flottante. Déjà. Si vous savez à l'avance que vous utilisez quatre décimales et que vous voulez "exact", vous devez traiter vos nombres en entier et les afficher uniquement sous forme de nombres décimaux. 944345 + 1111 = 945456

+2

"Vous n'allez pas obtenir une valeur" exacte "avec le point flottant." Oui vous l'êtes, si votre valeur est un nombre entier ou si la partie fractionnaire a un dénominateur qui est une puissance de 2. – JeremyP

+0

C'est un très petit sous-ensemble de la plage des nombres à virgule flottante. Mais si vous voulez dépendre de cela, allez-y. – hymie