J'écris actuellement un code dans lequel j'ai quelque chose comme:
double a = SomeCalculation1();
double b = SomeCalculation2();
if (a < b)
DoSomething2();
else if (a > b)
DoSomething3();
Et puis, dans d'autres endroits, je devrais peut-être faire l'égalité:
double a = SomeCalculation3();
double b = SomeCalculation4();
if (a == 0.0)
DoSomethingUseful(1 / a);
if (b == 0.0)
return 0; // or something else here
En bref, j'ai beaucoup de calculs en virgule flottante et je dois faire diverses comparaisons des conditions. Je ne peux pas le convertir en maths entier car une telle chose n'a pas de sens dans ce contexte.
J'ai déjà lu que les comparaisons en virgule flottante peuvent ne pas être fiables, car vous pouvez avoir des problèmes comme celui-ci:
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
En bref, je voudrais savoir: Comment puis-je comparer de manière fiable des nombres en virgule flottante (inférieurs à, supérieurs à, égaux)?
La plage de nombres que j'utilise varie approximativement de 10E-14 à 10E6. Je dois donc travailler avec des nombres petits et grands.
Je l'ai étiquetée comme étant agnostique, car je m'intéresse à la façon dont je peux y parvenir, quelle que soit la langue utilisée.
La comparaison entre grand et petit n'est pas vraiment un problème, à moins que vous ne travailliez exactement à la limite de la limite de flottement/double précision.
Pour une comparaison "fuzzy equals", ceci (le code Java devrait être facile à adapter) est ce que j'ai proposé pour Le Guide des points flottants après beaucoup de travail et en tenant compte de nombreuses critiques:
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / (absA + absB) < epsilon;
}
}
Il vient avec une suite de tests. Vous devez immédiatement rejeter toute solution qui ne le ferait pas, car dans certains cas Edge, son échec est pratiquement garanti, par exemple si vous avez une valeur 0, deux valeurs très petites opposées à zéro ou des infinis.
Une alternative (voir le lien ci-dessus pour plus de détails) consiste à convertir les modèles de bits des flotteurs en nombres entiers et à accepter tout ce qui se trouve dans une distance de nombre entier fixe.
Dans tous les cas, il n’ya probablement pas de solution parfaite pour toutes les applications. Idéalement, vous développeriez/adapteriez le vôtre avec une suite de tests couvrant vos cas d'utilisation réels.
J'ai eu le problème de comparer les nombres à virgule flottante A < B
et A > B
Voici ce qui semble fonctionner:
if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is less than B");
}
if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
printf("A is greater than B");
}
Les fabs - valeur absolue - s’occupe de leur égalité.
Nous devons choisir un niveau de tolérance pour comparer les nombres flottants. Par exemple,
final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
Console.WriteLine("Oh yes!");
Une note. Votre exemple est plutôt amusant.
double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
Console.WriteLine("Oh no!");
Quelques maths ici
a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.
1/3 != 1
Oh oui..
Tu veux dire
if (b != 1)
Console.WriteLine("Oh no!")
bool nearly_equal(
float a, float b,
float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
// those defaults are arbitrary and could be removed
{
assert(std::numeric_limits<float>::epsilon() <= epsilon);
assert(epsilon < 1.f);
if (a == b) return true;
auto diff = std::abs(a-b);
auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
return diff < std::max(relth, epsilon * norm);
}
Lors de la comparaison de nombres en virgule flottante, il existe deux "modes".
Le premier est le mode relatif, où la différence entre x
et y
est considérée par rapport à leur amplitude |x| + |y|
. Lorsque tracé en 2D, il donne le profil suivant, où le vert signifie l'égalité de x
et y
. (J'ai pris une epsilon
de 0,5 à des fins d'illustration).
Le mode relatif correspond à ce qui est utilisé pour les valeurs de points flottants "normales" ou "suffisamment grandes". (Plus sur cela plus tard).
Le second est un mode absolu, lorsque nous comparons simplement leur différence à un nombre fixe. Il donne le profil suivant (encore une fois avec une epsilon
de 0,5 et une relth
de 1 pour illustration).
Ce mode de comparaison absolu est utilisé pour les "minuscules" valeurs en virgule flottante.
Maintenant, la question est de savoir comment assembler ces deux modèles de réponse.
Dans la réponse de Michael Borgwardt, le changement est basé sur la valeur de diff
, qui devrait être inférieure à relth
(Float.MIN_NORMAL
dans sa réponse). Cette zone de commutation est hachurée dans le graphique ci-dessous.
Comme relth * epsilon
est plus petit que relth
, les patchs verts ne collent pas, ce qui confère à la solution une mauvaise propriété: nous pouvons trouver des triplets de nombres tels que x < y_1 < y_2
et pourtant x == y2
mais x != y1
.
Prenons cet exemple frappant:
x = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
Nous avons x < y1 < y2
, et en fait y2 - x
est plus de 2000 fois plus grand que y1 - x
. Et pourtant, avec la solution actuelle,
nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
En revanche, dans la solution proposée ci-dessus, la zone de commutation est basée sur la valeur de |x| + |y|
, qui est représentée par le carré hachuré ci-dessous. Cela garantit que les deux zones se connectent harmonieusement.
De plus, le code ci-dessus n'a pas de branche, ce qui pourrait être plus efficace. Considérez que des opérations telles que max
et abs
, qui a priori a besoin d'être ramifiées, ont souvent des instructions de montage dédiées. Pour cette raison, je pense que cette approche est supérieure à une autre solution qui consisterait à corriger la variable nearlyEqual
de Michael en modifiant le commutateur de diff < relth
à diff < eps * relth
, ce qui produirait essentiellement le même motif de réponse.
Le basculement entre ces modes est effectué autour de relth
, qui est pris comme FLT_MIN
dans la réponse acceptée. Ce choix signifie que la représentation de float32
est ce qui limite la précision de nos nombres à virgule flottante.
Cela n'a pas toujours de sens. Par exemple, si les chiffres que vous comparez sont les résultats d'une soustraction, il est peut-être plus judicieux d'utiliser quelque chose qui se situe dans la plage de FLT_EPSILON
. S'ils sont des racines carrées de nombres soustraits, l'imprécision numérique pourrait être encore plus grande.
Il est plutôt évident de comparer un nombre à virgule flottante avec 0
. Ici, toute comparaison relative échouera, car |x - 0| / (|x| + 0) = 1
. La comparaison doit donc passer en mode absolu lorsque x
est dans l’ordre de l’imprécision de votre calcul - et est rarement aussi bas que FLT_MIN
.
C’est la raison de l’introduction du paramètre relth
ci-dessus.
De plus, en ne multipliant pas relth
par epsilon
, l'interprétation de ce paramètre est simple et correspond au niveau de précision numérique que nous attendons de ces nombres.
(gardé ici surtout pour mon propre plaisir)
Plus généralement, je suppose qu'un opérateur de comparaison de virgule flottante bien conçu, =~
, devrait avoir certaines propriétés de base.
Les éléments suivants sont plutôt évidents:
a =~ a
a =~ b
implique b =~ a
a =~ b
implique -a =~ -b
(Nous n'avons pas a =~ b
et b =~ c
implique a =~ c
, =~
n'est pas une relation d'équivalence).
J'ajouterais les propriétés suivantes plus spécifiques aux comparaisons en virgule flottante
a < b < c
, alors a =~ c
implique a =~ b
(les valeurs les plus proches doivent également être égales)a, b, m >= 0
alors a =~ b
implique a + m =~ b + m
(des valeurs plus grandes avec la même différence devraient également être égales)0 <= λ < 1
alors a =~ b
implique λa =~ λb
(peut-être moins évident pour argumenter pour).Ces propriétés imposent déjà de fortes contraintes sur les éventuelles fonctions de quasi égalité. La fonction proposée ci-dessus les vérifie. Peut-être une ou plusieurs propriétés par ailleurs évidentes sont manquantes.
Quand on pense à =~
comme une famille de relations d’égalité =~[Ɛ,t]
paramétrée par Ɛ
et relth
, on peut aussi ajouter
Ɛ1 < Ɛ2
alors a =~[Ɛ1,t] b
implique a =~[Ɛ2,t] b
(une égalité pour une tolérance donnée implique une égalité avec une tolérance supérieure)t1 < t2
alors a =~[Ɛ,t1] b
implique a =~[Ɛ,t2] b
(l'égalité pour une imprécision donnée implique une égalité pour une imprécision plus élevée)La solution proposée les vérifie également.
Idée que j'avais pour la comparaison en virgule flottante dans Swift
infix operator ~= {}
func ~= (a: Float, b: Float) -> Bool {
return fabsf(a - b) < Float(FLT_EPSILON)
}
func ~= (a: CGFloat, b: CGFloat) -> Bool {
return fabs(a - b) < CGFloat(FLT_EPSILON)
}
func ~= (a: Double, b: Double) -> Bool {
return fabs(a - b) < Double(FLT_EPSILON)
}
Adaptation à PHP de Michael Borgwardt & bosonix's answer:
class Comparison
{
const MIN_NORMAL = 1.17549435E-38; //from Java Specs
// from http://floating-point-gui.de/errors/comparison/
public function nearlyEqual($a, $b, $epsilon = 0.000001)
{
$absA = abs($a);
$absB = abs($b);
$diff = abs($a - $b);
if ($a == $b) {
return true;
} else {
if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) {
return $diff < ($epsilon * self::MIN_NORMAL);
} else {
return $diff / ($absA + $absB) < $epsilon;
}
}
}
}
Vous devriez vous demander pourquoi vous comparez les chiffres. Si vous connaissez le but de la comparaison, vous devez également connaître l'exactitude requise de vos chiffres. C'est différent dans chaque situation et chaque contexte d'application. Mais dans presque tous les cas pratiques, il existe une précision requise absolue. Ce n’est que très rarement qu’une précision relative est applicable.
Pour donner un exemple: si votre objectif est de tracer un graphique à l'écran, vous voudrez probablement que les valeurs en virgule flottante soient comparées égales si elles correspondent au même pixel à l'écran. Si la taille de votre écran est de 1 000 pixels et que vos chiffres sont compris entre 1 et 6, vous voudrez probablement que 100 se compare à 200.
Compte tenu de la précision absolue requise, l’algorithme devient:
public static ComparisonResult compare(float a, float b, float accuracy)
{
if (isnan(a) || isnan(b)) // if NaN needs to be supported
return UNORDERED;
if (a == b) // short-cut and takes care of infinities
return EQUAL;
if (abs(a-b) < accuracy) // comparison wrt. the accuracy
return EQUAL;
if (a < b) // larger / smaller
return SMALLER;
else
return LARGER;
}
Le conseil standard consiste à utiliser une petite valeur "epsilon" (choisie en fonction de votre application, probablement), et à considérer les flottants situés dans epsilon les mêmes. par exemple. quelque chose comme
#define EPSILON 0.00000001
if ((a - b) < EPSILON && (b - a) < EPSILON) {
printf("a and b are about equal\n");
}
Une réponse plus complète est compliquée, car l’erreur en virgule flottante est extrêmement subtile et déroutante. Si vous vous souciez vraiment de l'égalité dans un sens précis, vous cherchez probablement une solution qui n'implique pas de virgule flottante.
J'ai essayé d'écrire une fonction d'égalité avec les commentaires ci-dessus à l'esprit. Voici ce que je suis venu avec:
Modifier: passez de Math.Max (a, b) à Math.Max (Math.Abs (a), Math.Abs (b))
static bool fpEqual(double a, double b)
{
double diff = Math.Abs(a - b);
double epsilon = Math.Max(Math.Abs(a), Math.Abs(b)) * Double.Epsilon;
return (diff < epsilon);
}
Pensées? Je dois encore travailler plus et moins bien.