c++ compiler wundef - Pourquoi Clang optimise-t-il x * 1.0 mais PAS x + 0.0?




1 Answers

La norme IEEE 754-2008 pour l'arithmétique à virgule flottante et la norme ISO / IEC 10967 sur l'arithmétique indépendante du langage (LIA), partie 1, expliquent pourquoi.

IEEE 754 § 6.3 Le bit de signe

Quand une entrée ou un résultat est NaN, cette norme n'interprète pas le signe d'un NaN. Notez cependant que les opérations sur les chaînes de bits - copy, negate, abs, copySign - spécifient le bit de signe d'un résultat NaN, parfois basé sur le bit de signe d'un opérande NaN. Le prédicat logique totalOrder est également affecté par le bit de signe d'un opérande NaN. Pour toutes les autres opérations, cette norme ne spécifie pas le bit de signe d'un résultat NaN, même lorsqu'il n'y a qu'une seule entrée NaN, ou lorsque le NaN est produit à partir d'une opération invalide.

Lorsque ni les entrées ni le résultat ne sont NaN, le signe d'un produit ou d'un quotient est le OU exclusif des signes des opérandes; le signe d'une somme, ou d'une différence x - y considérée comme une somme x + (-y), diffère d'au plus un des signes d'addition; et le signe du résultat des conversions, de l'opération de quantification, des opérations roundTo-Integral et du roundToIntegralExact (voir 5.3.1) est le signe du premier ou de l'unique opérande. Ces règles doivent s'appliquer même lorsque les opérandes ou les résultats sont nuls ou infinis.

Lorsque la somme de deux opérandes avec des signes opposés (ou la différence de deux opérandes avec des signes similaires) est exactement zéro, le signe de cette somme (ou différence) doit être +0 dans tous les attributs de direction d'arrondi sauf roundTowardNegative; sous cet attribut, le signe d'une somme nulle exacte (ou différence) doit être -0. Cependant, x + x = x - (-x) conserve le même signe que x même lorsque x est nul.

Le cas de l'addition

Sous le mode d'arrondi par défaut (Round-to-Nearest, Ties-to-Even) , nous voyons que x+0.0 produit x , SAUF lorsque x est -0.0 : Dans ce cas, nous avons une somme de deux opérandes avec des signes opposés est nul et §6.3 le paragraphe 3 règle que cette addition produit +0.0 .

Puisque +0.0 n'est pas identique au bit +0.0 original, et que -0.0 est une valeur légitime qui peut se produire en entrée, le compilateur est obligé de mettre dans le code qui va transformer les zéros négatifs potentiels en +0.0 .

Le résumé: Sous le mode d'arrondi par défaut, en x+0.0 , si x

  • n'est pas -0.0 , alors x lui-même est une valeur de sortie acceptable.
  • est -0.0 , alors la valeur de sortie doit être +0.0 , ce qui n'est pas identique au bit à -0.0 .

Le cas de la multiplication

Sous le mode d'arrondi par défaut , aucun problème de ce type ne se produit avec x*1.0 . Si x :

  • est un nombre (sub) normal, x*1.0 == x toujours.
  • est +/- infinity , le résultat est +/- infinity du même signe.
  • est NaN , puis selon

    IEEE 754 § 6.2.3 Propagation de NaN

    Une opération qui propage un opérande NaN à son résultat et qui a un seul NaN comme entrée devrait produire un NaN avec la charge utile de l'entrée NaN si elle est représentable dans le format de destination.

    ce qui signifie qu'il est recommandé que l'exposant et la mantisse (mais pas le signe) de NaN*1.0 soient inchangés par rapport à l'entrée NaN . Le signe n'est pas spécifié conformément au §6.3p1 ci-dessus, mais une implémentation peut spécifier qu'il soit identique à la source NaN .

  • est +/- 0.0 , le résultat est un 0 avec son bit de signe XORed avec le bit de signe de 1.0 , en accord avec le §6.3p2. Puisque le bit de signe de 1.0 est 0 , la valeur de sortie est inchangée par rapport à l'entrée. Ainsi, x*1.0 == x même lorsque x est un zéro (négatif).

Le cas de la soustraction

Sous le mode d'arrondi par défaut , la soustraction x-0.0 est également une opération non-op, car elle est équivalente à x + (-0.0) . Si x est

  • est NaN , alors §6.3p1 et §6.2.3 s'appliquent de la même manière que pour l'addition et la multiplication.
  • est +/- infinity , le résultat est +/- infinity du même signe.
  • est un nombre (sub) normal, x-0.0 == x toujours.
  • est -0.0 , alors par §6.3p2 nous avons " [...] le signe d'une somme, ou d'une différence x - y considérée comme une somme x + (-y), diffère d'au plus un des addenda" signes; ". Cela nous oblige à assigner -0.0 comme résultat de (-0.0) + (-0.0) , parce que -0.0 diffère de signe d' aucun des addends, alors que +0.0 diffère de signe de deux des addends, en violation de cette clause .
  • est +0.0 , alors ceci se réduit au cas d'addition (+0.0) + (-0.0) considéré ci-dessus dans The Case of Addition , qui selon §6.3p3 est considéré comme donnant +0.0 .

Puisque dans tous les cas la valeur d'entrée est légale comme sortie, il est permis de considérer x-0.0 a no-op, et x == x-0.0 a tautology.

Optimisations évolutives

La norme IEEE 754-2008 a la citation intéressante suivante:

IEEE 754 § 10.4 Signification littérale et optimisations changeantes

[...]

Les transformations de changement de valeur suivantes, entre autres, préservent la signification littérale du code source:

  • Appliquer la propriété d'identité 0 + x lorsque x n'est pas nul et n'est pas un NaN de signalisation et le résultat a le même exposant que x.
  • Appliquer la propriété d'identité 1 × x lorsque x n'est pas un NaN de signalisation et que le résultat a le même exposant que x.
  • Changer la charge utile ou le bit de signe d'un NaN silencieux.
  • [...]

Puisque tous les NaN et tous les infinis partagent le même exposant, et que le résultat correctement arrondi de x+0.0 et x*1.0 pour x fini a exactement la même grandeur que x , leur exposant est le même.

SNNAs

Les NaN de signalisation sont des valeurs de pièges à virgule flottante; Ce sont des valeurs NaN spéciales dont l'utilisation en tant qu'opérande à virgule flottante entraîne une exception d'opération invalide (SIGFPE). Si une boucle qui déclenche une exception est optimisée, le logiciel ne se comportera plus de la même manière.

Cependant, comme le signale l'utilisateur dans les commentaires , le standard C11 laisse explicitement indéfini le comportement des NaNs de signalisation ( sNaN ), ce qui permet au compilateur de supposer qu'ils ne se produisent pas et donc que les exceptions qu'ils déclenchent ne se produisent pas non plus. La norme C ++ 11 omet de décrire un comportement pour la signalisation de NaN, et la laisse donc indéfinie.

Modes d'arrondi

Dans les modes d'arrondi alternatifs, les optimisations autorisées peuvent changer. Par exemple, en mode Round-to-Negative-Infinity , l'optimisation x+0.0 -> x devient permise, mais x-0.0 -> x devient interdit.

Pour empêcher GCC d' -frounding-math comportements et des modes d'arrondi par défaut, l'indicateur expérimental -frounding-math peut être transmis à GCC.

Conclusion

Clang et GCC , même à -O3 , restent conformes à la norme IEEE-754. Cela signifie qu'il doit respecter les règles ci-dessus de la norme IEEE-754. x+0.0 n'est pas identique à x pour tout x vertu de ces règles, mais x*1.0 peut être choisi comme tel : À savoir, quand nous

  1. Obéissez à la recommandation de transmettre inchangé la charge utile de x lorsqu'il s'agit d'un NaN.
  2. Laissez le bit de signe d'un résultat NaN inchangé par * 1.0 .
  3. Obéissez l'ordre à XOR le bit de signe pendant un quotient / produit, quand x n'est pas un NaN.

Pour activer l'optimisation IEEE-754-safe (x+0.0) -> x , l' -ffast-math doit être passée à Clang ou à GCC.

pic commandline

Pourquoi Clang optimise la boucle dans ce code

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

mais pas la boucle dans ce code?

#include <time.h>
#include <stdio.h>

static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };

int main()
{
    clock_t const start = clock();
    for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
    printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}

(Marquage comme C et C ++ parce que je voudrais savoir si la réponse est différente pour chacun.)




Related